/*****************************************************************************
 * 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/28追加:'-xc','-xcs'オプションを追加しました。
//言語選択
static char* opt_x = "c";		//"c"=C言語形式,"cs"=C#形式
//}}2017/02/28追加:'-xc','-xcs'オプションを追加しました。
/*---------------------------------------------------------------------------*/
#define Label_Max     50000			//ラベル登録最大数	//調整可
#define CmdByte_Max 1000000			//命令バイト最大数	//調整可
static uint8_t TBL_CmdByte[CmdByte_Max];	//命令バイト配列
static int nCmdByte;				//命令バイト配列の格納数
static int iRepeatPos = -1/*未出現*/;		//最後に出現したrepeatの位置。TBL_CmdByte[]へのインデクスです。初期状態,及び,endが出現したら未出現(-1)に戻す。	//{{2017/02/11追加:ループ制御を追加しました。}}
static char* TBL_Label[Label_Max];		//ラベル配列
static int nLabel;				//ラベル配列の格納数
//ラベル名のソート方法
int (*fnLabCmp)(const void*, const void*) = strcmp;	//'n'オプションが指定されたらstrnatcmpに変更する。
/*---------------------------------------------------------------------------*/
//エラーログ用
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 uint8_t* add_cmd_byte(int n);
static void pass2(int bOptimize);
static void optimize();
static void output(char* sBinOut);
/*---------------------------------------------------------------------------*/
int main(int argc, char* argv[]) {
	int exitCode;
	SEH_init();
	exitCode = subr(argc, argv);
	return exitCode;
}
/*---------------------------------------------------------------------------*/
static int subr(int argc, char* argv[]) {
	int opt, bOptimize = 1;	//'-d'オプションが指定されなければ最適化を行う。
	char* sBinOut = NULL;	//'-o'オプションが指定されたらバイナリ出力ファイル名を設定する。
#if     (YYDEBUG!=0)
//	yydebug = 1; //nonzero means print parse trace
#endif/*(YYDEBUG!=0)*/
	//コマンドラインオプションを処理する。
	opterr = 0; //オプションエラーは当プログラム内で処理するので、getopt()内でのエラー表示は不要。
	while((opt = getopt(argc, argv, "do:enw::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/28追加:'-xc','-xcs'オプションを追加しました。
		case 'x':
			opt_x = optarg;		//言語選択を設定する。
			if(strcmp(opt_x, "c") && strcmp(opt_x, "cs")) { usage(); }
			break;
//}}2017/02/28追加:'-xc','-xcs'オプションを追加しました。
		case 'q':
			/** no job **/	//winapp.hの'Fri Sep 16 21:30:10 JST 2016'のコメントを参照して下さい。
			break;
		case 'h':
		default:
			usage();
		}
	}
	//ラベルを登録するためのハッシュテーブルを作成する。
	hcreate(Label_Max);
	//判り易さのために、命令位置0は使用しないことにした。命令位置0には、ダミーのend命令を置くことにした。
	inst_end();
	//--- 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);
		}
	}
	//1パス目の処理でエラーが発生しても、2パス目の処理を行って、全てのセマンティクスエラーを報告する。
	//--- 2パス目の処理 ---
	pass2(bOptimize);
	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, "dTapSeqC - TAPシーケンサコンパイラ (%s)\n", VERSION);
	fprintf(stderr, "Copyright (C) 2015-2017 Naoyuki Sawa\n");
	fprintf(stderr, "\n");
	fprintf(stderr, "USAGE:\n");
	fprintf(stderr, "  dTapSeqC [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ファイル名 TAP制御のバイナリデータを、バイナリ形式でファイルへ出力します。\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/28追加:'-xc','-xcs'オプションを追加しました。
	fprintf(stderr, "  -xc          出力ソースをC言語形式とします。(既定値)\n");
	fprintf(stderr, "  -xcs         出力ソースをC#形式とします。\n");
//}}2017/02/28追加:'-xc','-xcs'オプションを追加しました。
	fprintf(stderr, "  -q           進捗表示を抑制します。\n");
	fprintf(stderr, "  -h           使い方を表示します。\n");
	fprintf(stderr, "\n");
	fprintf(stderr, "EXAMPLE:\n");
	fprintf(stderr, "  ①dTapSeqC.exe TapDef.txt > TapDef.c\n");
	fprintf(stderr, "  　TapDef.txtをコンパイルして、TapDef.cへ出力します。\n");
	fprintf(stderr, "  ②dTapSeqC.exe -oTapDef.bin TapDef.txt > TapDef.h\n");
	fprintf(stderr, "  　TapDef.txtをコンパイルして、ラベルをTapDef.hへ出力します。\n");
	fprintf(stderr, "  　TAP制御のバイナリデータを、TapDef.binへ出力します。\n");
	exit(EXIT_FAILURE);
}
/*---------------------------------------------------------------------------*/
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;
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
static uint8_t* add_cmd_byte(int n) {
	uint8_t* p;
	if((unsigned)(nCmdByte + n) > CmdByte_Max) {
		yyerror("命令バイト数が制限を超えました。(%d)", CmdByte_Max);
		exit(EXIT_FAILURE);
	}
	p = &TBL_CmdByte[nCmdByte];
	nCmdByte += n;
	return p;
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
void inst_def(char* pLabel) {
	ENTRY item;
	item.key = (char*)pLabel;
	if(hsearch(item, FIND)) {
		yyerror("defのラベル定義が重複しています。(%s)", pLabel);
		yynerrs++;	//呼び出し元(main)に知らせるために。
		//全てのセマンティクスエラーを報告するために継続する。
	}
	item.data = (void*)nCmdByte;
	if(!hsearch(item, ENTER)) {
		yyerror("ラベル数が制限を超えました。(%d)", Label_Max);
		yynerrs++;	//呼び出し元(main)に知らせるために。
		//全てのセマンティクスエラーを報告するために継続する。
	}
	if(nLabel < Label_Max) {
		TBL_Label[nLabel++] = pLabel;	//hsearch()はイテレーションの機能が無いので、後でイテレートするためにはキー配列を別に作成しておく必要が有る。
	}
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
//{{2017/02/11追加:ループ制御を追加しました。
void inst_repeat() {
	//前回のrepeatが出現した後、endが出現する前に、再度repeatが出現したら…
	if(iRepeatPos != -1) {
		yyerror("repeatとendの対応が不正です。");
		yynerrs++;	//呼び出し元(main)に知らせるために。
		//全てのセマンティクスエラーを報告するために継続する。
	}
	//最後に出現したrepeatの位置を記憶する。
	iRepeatPos = nCmdByte;
}
//}}2017/02/11追加:ループ制御を追加しました。
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
void inst_end() {
	uint8_t* p;
//{{2017/02/11追加:ループ制御を追加しました。
	//最後に出現したrepeatが有れば…
	if(iRepeatPos != -1) {
		//現在の位置から、最後に出現したrepeatの位置への、逆方向の距離を求める。
		int iDist = nCmdByte - iRepeatPos;
		if(iDist <= 0) { DIE(); }	//バグ
		//距離によって、1～4バイトジャンプのコマンドを生成する。
	    //{{1～4バイトジャンプのエンコード方法は、既存のinst_wait()における1～4バイトウェイトのエンコード方法と同じです。
		       if((iDist >= 0x00001) && (iDist <= (0x00001 + 0x000000FF))) {
			   iDist -= 0x00001;
			p = add_cmd_byte(2);
			p[0] = 0x11;	//1バイトジャンプ
			p[1] = iDist;
		} else if((iDist >= 0x00101) && (iDist <= (0x00101 + 0x0000FFFF))) {
			   iDist -= 0x00101;
			p = add_cmd_byte(3);
			p[0] = 0x12;	//2バイトジャンプ
			p[1] = iDist >>  8;
			p[2] = iDist;
		} else if((iDist >= 0x10101) && (iDist <= (0x10101 + 0x00FFFFFF))) {
			   iDist -= 0x10101;
			p = add_cmd_byte(4);
			p[0] = 0x13;	//3バイトジャンプ
			p[1] = iDist >> 16;
			p[2] = iDist >>  8;
			p[3] = iDist;
		} else {
			p = add_cmd_byte(5);
			p[0] = 0x14;	//4バイトジャンプ
			p[1] = iDist >> 24;
			p[2] = iDist >> 16;
			p[3] = iDist >>  8;
			p[4] = iDist;
		}
	    //}}1～4バイトジャンプのエンコード方法は、既存のinst_wait()における1～4バイトウェイトのエンコード方法と同じです。
		//最後に出現したrepeatの位置をクリアする。
		iRepeatPos = -1;
		return;	//ここまで
	}
//}}2017/02/11追加:ループ制御を追加しました。
	p = add_cmd_byte(1);
	p[0] = 0x00;	//シーケンスデータの終端
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
void inst_wait(int iTime) {
	uint8_t* p;
	       if((iTime >= 0x00001) && (iTime <= (0x00001 + 0x000000FF))) {
		   iTime -= 0x00001;
		p = add_cmd_byte(2);
		p[0] = 0x01;	//1バイトウェイト
		p[1] = iTime;
	} else if((iTime >= 0x00101) && (iTime <= (0x00101 + 0x0000FFFF))) {
		   iTime -= 0x00101;
		p = add_cmd_byte(3);
		p[0] = 0x02;	//2バイトウェイト
		p[1] = iTime >>  8;
		p[2] = iTime;
	} else if((iTime >= 0x10101) && (iTime <= (0x10101 + 0x00FFFFFF))) {
		   iTime -= 0x10101;
		p = add_cmd_byte(4);
		p[0] = 0x03;	//3バイトウェイト
		p[1] = iTime >> 16;
		p[2] = iTime >>  8;
		p[3] = iTime;
	} else {
		p = add_cmd_byte(5);
		p[0] = 0x04;	//4バイトウェイト
		p[1] = iTime >> 24;
		p[2] = iTime >> 16;
		p[3] = iTime >>  8;
		p[4] = iTime;
	}
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
void inst_ctrl(int iLogCh, int iAtt, int iPan, int fMask) {
	uint8_t* p;
	if((unsigned)iLogCh > (UINT8_MAX-1)) {
		yyerror("iAttの引数iLogChが範囲外です。(%d)", iLogCh);
		yynerrs++;	//呼び出し元(main)に知らせるために。
		//全てのセマンティクスエラーを報告するために継続する。
	}
	if((unsigned)iAtt > UINT8_MAX) {	//0～255
		yyerror("iAttの引数iAttが範囲外です。(%d)", iAtt);
		yynerrs++;	//呼び出し元(main)に知らせるために。
		//全てのセマンティクスエラーを報告するために継続する。
	}
	if((iPan < INT8_MIN) || (iPan > INT8_MAX)) {	//-128～127
		yyerror("playの引数iPanが範囲外です。(%d)", iPan);
		yynerrs++;	//呼び出し元(main)に知らせるために。
		//全てのセマンティクスエラーを報告するために継続する。
	}
	if((fMask < 1) || (fMask > 3)) { DIE(); }	//バグ
	p = add_cmd_byte(2);
	p[0] = 0x05 + (fMask - 1);	//0x05～0x07	コントロールチェンジ
	p[1] = iLogCh;
	if(fMask & 1) { *add_cmd_byte(1) = iAtt;  }	//0～255
	if(fMask & 2) { *add_cmd_byte(1) = iPan;  }	//-128～127
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
//{{2017/02/13変更:'play.w'命令を追加しました。
//void inst_play(int iLogCh, int iPhr, int iLoop, int iAtt, int iPan) {
//↓2017/02/13変更:'play.w'命令を追加しました。
void inst_play(int iLogCh, int iPhr, int iLoop, int iAtt, int iPan, int bWait) {
//}}2017/02/13変更:'play.w'命令を追加しました。
	int fMask;
	uint8_t* p;
	if((unsigned)iLogCh > (UINT8_MAX-1)) {	//0～254
		yyerror("playの引数iLogChが範囲外です。(%d)", iLogCh);
		yynerrs++;	//呼び出し元(main)に知らせるために。
		//全てのセマンティクスエラーを報告するために継続する。
	}
	if((unsigned)iPhr > (UINT16_MAX-1)) {	//0～65534
		yyerror("playの引数iPhrが範囲外です。(%d)", iPhr);
		yynerrs++;	//呼び出し元(main)に知らせるために。
		//全てのセマンティクスエラーを報告するために継続する。
	}
	if((unsigned)iLoop > UINT8_MAX) {	//0=無限ループ,1～255=ループ回数
		yyerror("playの引数iLoopが範囲外です。(%d)", iLoop);
		yynerrs++;	//呼び出し元(main)に知らせるために。
		//全てのセマンティクスエラーを報告するために継続する。
	}
	if((unsigned)iAtt > UINT8_MAX) {	//0～255
		yyerror("playの引数iAttが範囲外です。(%d)", iAtt);
		yynerrs++;	//呼び出し元(main)に知らせるために。
		//全てのセマンティクスエラーを報告するために継続する。
	}
	if((iPan < INT8_MIN) || (iPan > INT8_MAX)) {	//-128～127
		yyerror("playの引数iPanが範囲外です。(%d)", iPan);
		yynerrs++;	//呼び出し元(main)に知らせるために。
		//全てのセマンティクスエラーを報告するために継続する。
	}
	                 fMask  = 0;
	if(iLoop != 1) { fMask |= 1; }	//データ容量を節約するためにデフォルト値と異なる場合のみ出力する事にした。ちなみにinst_ctrl()の方ではこうしてはいけない。inst_ctrl()の方では別の値からデフォルト値へ明示的に変化させる場合も有るから。
	if(iAtt  != 0) { fMask |= 2; }	//データ容量を節約するためにデフォルト値と異なる場合のみ出力する事にした。ちなみにinst_ctrl()の方ではこうしてはいけない。inst_ctrl()の方では別の値からデフォルト値へ明示的に変化させる場合も有るから。
	if(iPan  != 0) { fMask |= 4; }	//データ容量を節約するためにデフォルト値と異なる場合のみ出力する事にした。ちなみにinst_ctrl()の方ではこうしてはいけない。inst_ctrl()の方では別の値からデフォルト値へ明示的に変化させる場合も有るから。
	if((fMask < 0) || (fMask > 7)) { DIE(); }	//バグ
	p = add_cmd_byte(4);
//{{2017/02/13変更:'play.w'命令を追加しました。
//	p[0] = 0x08 + fMask;	//0x08～0x0F	論理チャネル再生開始
//↓2017/02/13変更:'play.w'命令を追加しました。
	if(!bWait) {
		p[0] = 0x08 + fMask;	//0x08～0x0F	論理チャネル再生開始
	} else {
		p[0] = 0x18 + fMask;	//0x18～0x1F	論理チャネル再生開始(ウェイト付き)
	}
//}}2017/02/13変更:'play.w'命令を追加しました。
	p[1] = iLogCh;		//0～254
	p[2] = iPhr >> 8;	//┬0～65534
	p[3] = iPhr;		//┘
	if(fMask & 1) { *add_cmd_byte(1) = iLoop; }	//0～255
	if(fMask & 2) { *add_cmd_byte(1) = iAtt;  }	//0～255
	if(fMask & 4) { *add_cmd_byte(1) = iPan;  }	//-128～127
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
void inst_stop(int iLogCh) {
	uint8_t* p;
	if((unsigned)iLogCh > (UINT8_MAX-1)) {	//0～254
		yyerror("stopの引数iLogChが範囲外です。(%d)", iLogCh);
		yynerrs++;	//呼び出し元(main)に知らせるために。
		//全てのセマンティクスエラーを報告するために継続する。
	}
	p = add_cmd_byte(2);
	p[0] = 0x10;	//論理チャネル再生停止
	p[1] = iLogCh;
}
/*---------------------------------------------------------------------------*/
//2パス目の処理
static void pass2(int bOptimize) {
	//2015/02/24現在、2パス目の処理は有りません。
	//今後、最適化機能を追加する場合のためのプレースホルダとして、関数のみ用意して有ります。
	//現在のところは、コマンドラインオプションに'-d'を指定してもしなくても違いは有りません。
	if(bOptimize) { optimize(); }
}
static void optimize() {
	//2015/02/24現在、最適化処理は有りません。
	//今後、最適化機能を追加する場合のためのプレースホルダとして、関数のみ用意して有ります。
	//現在のところは、コマンドラインオプションに'-d'を指定してもしなくても違いは有りません。
}
/*---------------------------------------------------------------------------*/
//出力処理
static int Label_compar(const void* x, const void* y) {
	return (*fnLabCmp)(*(char**)x, *(char**)y);
}
static void output(char* sBinOut) {
	int iCmdByte, iLabel;
	ENTRY item, *pEntry;
	//ラベル定義を名前の昇順にソートする。
	qsort(TBL_Label, nLabel, sizeof(char*), Label_compar);
	//--- ラベルを標準出力に出力する ---
//{{2017/02/28変更:'-xc','-xcs'オプションを追加しました。
//	for(iLabel = 0; iLabel < nLabel; iLabel++) {				//名前の昇順	ラベル=0(ダミー)は定義しない。
//		char* label = TBL_Label[iLabel];
//		printf("#define TapSeqNo_%s %d\n", label, iLabel + 1);		//ラベルは1ベース
//	}
//	printf("#define TapSeqNo_Max %d\n", nLabel);				//最大値
//↓2017/02/28変更:'-xc','-xcs'オプションを追加しました。
	//C言語形式
	if(!strcmp(opt_x, "c")) {
		for(iLabel = 0; iLabel < nLabel; iLabel++) {				//名前の昇順	ラベル=0(ダミー)は定義しない。
			char* label = TBL_Label[iLabel];
			printf("#define TapSeqNo_%s %d\n", label, iLabel + 1);		//ラベルは1ベース
		}
		printf("#define TapSeqNo_Max %d\n", nLabel);				//最大値
	//C#形式
	} else if(!strcmp(opt_x, "cs")) {
		puts("static partial class Const {");
		for(iLabel = 0; iLabel < nLabel; iLabel++) {				//名前の昇順	ラベル=0(ダミー)は定義しない。
			char* label = TBL_Label[iLabel];
			printf("public const int TapSeqNo_%s = %d;\n", label, iLabel + 1);	//ラベルは1ベース
		}
		printf("public const int TapSeqNo_Max = %d;\n", nLabel);		//最大値
		puts("}");
	} else {
		DIE();	//バグ
	}
//}}2017/02/28変更:'-xc','-xcs'オプションを追加しました。
	//--- データを出力する ---
	//バイナリ形式の場合…
	if(sBinOut) {
		FILE* fp = fopen(sBinOut, "wb");
		if(!fp) { error(EXIT_FAILURE, errno, "%s が作成できません。", sBinOut); }	//{{2016/09/21変更:die(～)で終了していた箇所を、error(EXIT_FAILURE,～)で終了するように変更しました。}}	//{{2016/09/28変更:error()の文字列の最後に余計な改行が付いていたのを削除しました。}}
		//アドレステーブルを出力する。
		for(iLabel = -1; iLabel < nLabel; iLabel++) {
			int addr;
			if(iLabel < 0) {
				addr = nLabel + 1;				//TBL_TapSeqNo[0]=(TapSeqNo_Max+1)	ラベルは1ベースなのでnLabelではなく(nLabel+1)とする事に注意せよ。
			} else {
				char* label = TBL_Label[iLabel];
				item.key = label;
				if(!(pEntry = hsearch(item, FIND))) { DIE(); }	//バグ
				addr = (int)pEntry->data;
			}
			fwrite(&addr, sizeof(int), 1, fp);
		}
		//命令コードの出力を行う。
		for(iCmdByte = 0; iCmdByte < nCmdByte; iCmdByte++) {
			int data = TBL_CmdByte[iCmdByte];
			fwrite(&data, sizeof(unsigned char), 1, fp);
		}
		fclose(fp);
	//テキスト形式の場合…
	} else {
//{{2017/02/28追加:'-xc','-xcs'オプションを追加しました。
		//C#形式
		if(!strcmp(opt_x, "cs")) {
			puts("static partial class Const {");
		}
//}}2017/02/28追加:'-xc','-xcs'オプションを追加しました。
		//アドレステーブルを出力する。
//{{2017/02/28変更:'-xc','-xcs'オプションを追加しました。
//		printf("const int TBL_TapSeqNo[%d]={", nLabel + 1);
//↓2017/02/28変更:'-xc','-xcs'オプションを追加しました。
		//C言語形式
		if(!strcmp(opt_x, "c")) {
			printf("const int TBL_TapSeqNo[%d]={", nLabel + 1);
		//C#形式
		} else if(!strcmp(opt_x, "cs")) {
			printf("public static readonly int[/*%d*/] TBL_TapSeqNo={", nLabel + 1);
		} else {
			DIE();	//バグ
		}
//}}2017/02/28変更:'-xc','-xcs'オプションを追加しました。
		for(iLabel = -1; iLabel < nLabel; iLabel++) {
			int addr;
			if(iLabel < 0) {
				addr = nLabel + 1;				//TBL_TapSeqNo[0]=(TapSeqNo_Max+1)	ラベルは1ベースなのでnLabelではなく(nLabel+1)とする事に注意せよ。
			} else {
				char* label = TBL_Label[iLabel];
				item.key = label;
				if(!(pEntry = hsearch(item, FIND))) { DIE(); }	//バグ
				addr = (int)pEntry->data;
			}
			if((iLabel & 15) == 15)    { putchar('\n'); }
			printf("0x%08X", addr);
			if(iLabel != (nLabel - 1)) { putchar(',' ); }
		}
		puts("};");
		//命令コードの出力を行う。
//{{2017/02/28変更:'-xc','-xcs'オプションを追加しました。
//		printf("const unsigned char TBL_TapSeq[%d]={", nCmdByte);
//↓2017/02/28変更:'-xc','-xcs'オプションを追加しました。
		//C言語形式
		if(!strcmp(opt_x, "c")) {
			printf("const unsigned char TBL_TapSeq[%d]={", nCmdByte);
		//C#形式
		} else if(!strcmp(opt_x, "cs")) {
			printf("public static readonly byte[/*%d*/] TBL_TapSeq={", nCmdByte);
		} else {
			DIE();	//バグ
		}
//}}2017/02/28変更:'-xc','-xcs'オプションを追加しました。
		for(iCmdByte = 0; iCmdByte < nCmdByte; iCmdByte++) {
			int data = TBL_CmdByte[iCmdByte];
			if(  iCmdByte      ) { putchar(',' ); }
			if(!(iCmdByte & 31)) { putchar('\n'); }
			printf("0x%02X", data);
		}
		puts("};");
//{{2017/02/28追加:'-xc','-xcs'オプションを追加しました。
		//C#形式
		if(!strcmp(opt_x, "cs")) {
			puts("}");
		}
//}}2017/02/28追加:'-xc','-xcs'オプションを追加しました。
	}
}
