/*****************************************************************************/
/* ＳＴＫ内部コマンド：制御構文１（切り放し可）*/
/*****************************************************************************/

#include "stk.h"

extern STKSYS *stksys;
extern STKCOM koutbl[]; /* 中間コードを参照するため */

/*****************************************************************************/
/* ラベルスタックの積まれ方

	       Label. *-> LRET
	       switch *-> LSW    〜 send
	       repeat *-> LREP   〜 until
	       while  *-> LWHILE 〜 wend
	start end for *-> LFOR   〜 next
	~~~~~~~~~->rstack
	
	このように、制御構文中ではラベル・ループスタックが使われるので、
	その中からgotoで飛び出すときには、スタック補正が必要となる。
	（実際には、ループの２回目以降はループスタックは参照せず直接飛ぶ。）
	ちなみに、if〜endifから飛び出すときは何もしなくていい。
*/
/*****************************************************************************/

static int Sfor(NSTACK nstk[],int mode,unchar *text,unchar *(*jump),unchar *(*go))
/* start(1) end(0) FOR/FOR. 〜 （NEXT） → なし */
{
int ret;
	// for はend  までループする(start<=end)
	// for.はend-1までループする(start<end)
	if (mode) NN0--;
	if (NN1>NN0) { /* start>end → ループしない */
		if (*jump==NULL) { /* ジャンプ先がわかっていないとき */
		    unchar *btext;
			/* nextの次まで飛ばす */
			// FOR:2/FOR.:1 , NEXT:3
			if (STKSearchPair(koutbl,2,1,3,0,&text,&btext)) return(FORERR);
			*jump=text;
		}
		*go=*jump; // next直後のポイントへ飛ぶ
		return(LOK);
	}
	/* 初期値/終了値をrstackに積む */
	ret=PushRStack(NN1,NN0);
	// lstack:forの次のテキストポイントを記録
	return(ret? ret:PushLStack(text,LFOR));
}

static int Srepw(NSTACK nstk[],int mode,unchar *text)
/* REPEAT 〜 式 UNTIL／WHILE 式 〜 WEND */
{
	// lstack:repeat/whileの次のテキストポイント＝ループの戻りポイントを記録
	return(PushLStack(text,mode? LWHILE:LREP));
}

static int Swuntil(NSTACK nstk[],int mode,unchar *text,unchar *(*jump),unchar *(*go))
//（REPEAT 〜 式(0)）UNTIL : 式が成り立つまでループ
//（WHILE  〜 ）WEND : 基本的には無限ループで、IF. BREAKでloop outする
//（start(1) end(0) FOR） 〜 NEXT → なし : startがend+1になるまでループ
	/* ループを抜ける時、ループスタックを消す */
{
int ret;
char atrb;
	if (*jump==NULL) { // ジャンプ先がわかっていないとき=最初のループ
		// 戻りアドレスを参照&設定
		if ((ret=PopLStack(jump,&atrb,1))) return(ret);
		// ループの整合性をチェック
		switch (mode) {
		    case 0: // until
			if (atrb!=LREP) return(REPERR);
			break;
		    case 1: // wend
			if (atrb!=LWHILE) return(WHILEERR);
			break;
		    case 2: // next
			if (atrb!=LFOR) return(FORERR);
			break;
		}
	} // ２回目以降はわかっているジャンプアドレスへ直接飛ぶ
	  // ただしlstackはbreak/continueで使うのでループ終了まで残す
	
	switch (mode) {
	  unchar *adrs;
	  long i,end;
	    case 2: // next
		// ループカウンターチェック
		if ((ret=PopRStack(&i,&end,0))) return(ret);
		if (i<end) { // ループ中
			// さっき読み出せたということは積めるはずだからエラーチェックしない
			PushRStack(i+1,end);
			break;
		}
		// 現在通ってきたループで終わり
		goto LEND;
	    case 0: // until
		// 式が成り立っているかどうか
		if (!NN0) break; // 成り立っていない → ループ
		// 成り立っている → ループ終了
	    LEND:;
		// ループスタックを消して終了
		return(PopLStack(&adrs,&atrb,0));
	} // wendは無条件ループなので何もしない
	// ループ継続
	*go=*jump;
	return(LOK);
}

/*****************************************************************************/

static int STKSearchCase(long no,unchar *ctext,unchar *(*ntext))
/* 対応するsendまでの間にある、適合するcase/defaultを探す */
// 中間コード構造依存有り
{
int nest=1; /* 最初のswitchの分 */
unchar c,len,*dtext=NULL,*btext,*text=NULL;
STKCOM *tbl;
STKSYS *sys;
	sys=stksys; // 省メモリーのためローカル変数に置き換える
	do {
	    btext=text; /* １つ前の中間コードアドレス */
	    text=ctext; /* これから始まる中間コードのアドレス */
	    c=*ctext++;
	    if (c>SCCMD) { /* コマンド以外はスキップ */
		if (c==SCEND) return(NOSEND); // ﾃｷｽﾄが終わった=sendがない
		ctext+=EvenLen(*ctext++); // 偶数バイト化付き
		continue;
	    }
	    /* 比較対象はコマンドのみ */
	    len=*ctext++;
	    tbl=*(STKCOM **)ctext;
	    ctext+=len;
	    if (tbl!=koutbl) continue; // fnck以外のテーブルは無視
	    // if (c&FJUMP) c^=FJUMP;
	    c&=(~FJUMP);
	    switch (c) {
		// switch〜send対応チェック
		case 8: /* switch */
		    nest++;
		    break;
		case 9: /* send */
		    nest--;
		    break;
		
		/* case/defaultはnest=1のレベルのみ有効 */
		case 11:/* default */
		    if (nest==1) { // とりあえず記録
			if (dtext!=NULL) return(DEFAULTDBL); /* 多重にある */
			dtext=ctext; /* defaultの次のアドレス */
		    }
		    break;
		case 10:/* num case */
		    if (nest==1) { // CASE直前のコードを調べる
		      long num;
			switch (*btext++) {
			    default: // 数値と変数以外はエラー
				return(CASEERR);
			    case SCNUM: /* 数値 */
				num=*(long *)(btext+1); // lenを飛ばしてから
				break;
			    case SCVAR: /* 変数 */
				{ NSTACK ns;
				if (((STKVarRead(*(long *)(btext+1),&ns)>>8)&VTYPE)!=VNUM) {
					return(ARGILL); // 数値以外はエラー
				}
				num=ns.num;
				}
				break;
			}
			if (num==no) { // 一致
				*ntext=ctext; // caseの次へ
				return(OK);
			}
			// 一致しなかった時は無視
		    }
		    break;
	    }
	    // 対応したsendに行くまで
	} while (nest>0);
	// 一致したCASEはない
	if (nest<0) return(SENDOVER); // sendが多い
	// defaultがあるときはそこ=dtextに飛ぶ
	// defaultもないときはsendまで行く
	// send(=text)は必ず通る:switchで積んだﾗﾍﾞﾙｽﾀｯｸを消すため
	*ntext=(dtext!=NULL? dtext:text);
	return(OK);
}

/*---------------------------------------------------------------------------*/

static int Sswitch(NSTACK nstk[],int mode,unchar *text,unchar *(*jump),unchar *(*go))
/* 式(0) SWITCH 〜 SEND → なし */
{
int ret;
	switch (mode) {
	    case 1: /* send : SWITCHで積んでいるラベルスタックを消す */
		{ unchar *ntext; char atrb;
		if ((ret=PopLStack(&ntext,&atrb,0))) return(ret);
		if (atrb!=LSW) return(NOSEND);
		}
		// return(OK);
	    case 2: // num case : numは消される
	    case 3: // default  : 単独で出てきた時は何もしない
		return(OK);
	}
	/* switch */
	/* sendまでの間のcase/defaultをサーチする */
	// 移動先はCASE/DEFAULT
	if ((ret=STKSearchCase(NN0,text,go))) return(ret);
	
	/* switchの次のテキストポイントを記録（breakのため）*/
	ret=PushLStack(text,LSW);
	return(ret? ret:LOK);
}

/*****************************************************************************/

static int Sbreak(NSTACK nstk[],int mode,unchar *text,unchar *(*jump),unchar *(*go))
// mode=0 : BREAK , mode=1 : CONTINUE → なし
// +10 : drop付き
{
short fdrop=0;
int ret,bs,bs2=0;
char atrb;
unchar *adrs;
STKSYS *sys;
	sys=stksys; // 省メモリーのためローカル変数に置き換える
	if (mode>=10) {fdrop=1; mode-=10;} // drop付き mode
	if (mode) { // continueはlstack参照のみ
	    short ltop;
		/* switchにcontinueはない
		   この場合、１つ上のラベルスタックを読みだし、
			そこがループならそのループをcontinueする。
			このとき、switchは自動的にbreakされる。
		*/
		do {
			ltop=sys->ltop;
			if ((ret=PopLStack(&adrs,&atrb,0))) return(ret);
		} while (atrb==LSW);
		sys->ltop=ltop; // 最後の１回はループのはずなので戻す
	} else { // breakはlstackを読み出して消す
		if ((ret=PopLStack(&adrs,&atrb,0))) return(ret);
	}
	
	/* サーチ対象セット */
	switch (atrb) {
	    case LFOR:	/* for〜next */
		if (!mode) {
		    long i,end;
			/* FOR〜NEXTのBREAKではﾙｰﾌﾟｽﾀｯｸも消す */
			if ((ret=PopRStack(&i,&end,0))) return(ret);
		}
		bs=2; bs2=1;
		break;
	    case LWHILE:/* while〜wend */
		bs=4;
		break;
	    case LREP:	/* repeat〜until */
		// repeat〜untilのcontinueは、untilの条件判断残る。
		// このため、0 continueとする必要がある。
		bs=6;
		break;
	    case LSW:	/* switch〜send */
		/* switchにはcontinueはない */
		bs=8;
		break;
	    default:
	    brkerr:;
		return(mode? CONTERR:BRKERR);
	}
	/* 終了ポイントをサーチ */
	/* １つのループに２つ以上終了ポイントがある、ループが交差するという
		ことはできない。実行時にエラーになる。*/
	/* start:bs/bs2 , end:bs+1 */
	if (*jump==NULL) { /* ジャンプ先がわかっていないとき */
	    unchar *btext;
		if (STKSearchPair(koutbl,bs,bs2,bs+1,0,&text,&btext)) goto brkerr;
		/* continueはループ終了コマンドそのもの、
		      breakはループ終了コマンドの次へ飛ぶ */
		*jump=(mode? btext:text);
	}
	*go=*jump;
	if (fdrop) { // drop
	    NSTACK ns;
		if (PopNStack(&ns)) return(NSTKEMP);
	}
	return(LOK);
}

/*****************************************************************************/

// ここの中だけで特有なエラー
static ERRMES ERRkoubun[]={
	 {REPERR	,"REPEAT〜UNTIL異常"}
	,{FORERR	,"FOR〜NEXT異常"}
	,{WHILEERR	,"WHILE〜WEND異常"}
	,{NOSEND	,"SENDがない"}
	,{DEFAULTDBL	,"DEFAULTが多重"}
	,{CASEERR	,"CASE異常"}
	,{CASENUM	,"CASEの前に数値/変数がない"}
	,{BRKERR	,"BREAK異常"}
	,{CONTERR	,"CONTINUE異常"}
	,{SENDOVER	,"SENDが多過ぎる"}
	,{0,NULL}
};
static ERLINK erlist={ERRkoubun,NULL};

static int KobunInit()
/* 初期化 */
{
	return(STKErrLink(&erlist));
}

static int KobunEnd()
/* 終了 */
{
	return(stksys->rtop>0? RSTKERR:OK); /* ループが残っている */
}

/*****************************************************************************/
/* コマンドテーブル */
/* 処理内で[n]を参照するのでこの位置を変えないこと */
/* すべて[n]と[n+1]がペアになっている */

STKCOM koutbl[]={
	 {NULL		,KobunInit,TNONE,0,0}
	,{"FOR."	,Sfor	,TNUM TNUM,CFJUMP,1}	// 初 終 for. next
	,{"FOR"		,Sfor	,TNUM TNUM,CFJUMP,0}	// 初 終 for next
	,{"NEXT"	,Swuntil,TNONE	,CFJUMP,2}
	,{"WHILE"	,Srepw	,TNONE	,0,1}		// while wend
	,{"WEND"	,Swuntil,TNONE	,CFJUMP,1}
	,{"REPEAT"	,Srepw	,TNONE	,0,0} 		// repeat 式 until
	,{"UNTIL"	,Swuntil,TNUM	,CFJUMP,0}
	// switchは式によって飛び先が変わるのでCFJUMPは使えない
	,{"SWITCH"	,Sswitch,TNUM	,0,0}		// 式 switch send
	,{"SEND"	,Sswitch,TNONE	,0,1}
	,{"CASE"	,Sswitch,TNUM	,0,2}		// num case
	,{"DEFAULT"	,Sswitch,TNONE	,0,3}
	/**/
	,{"BREAK"	,Sbreak	,TNONE	,CFJUMP,0}	// ループ/switch カット
	,{"BREAK,"	,Sbreak	,TNONE	,CFJUMP,10}
	,{"CONTINUE"	,Sbreak	,TNONE	,CFJUMP,1}	// ループ継続
	,{"CONTINUE,"	,Sbreak	,TNONE	,CFJUMP,11}
	,{NULL		,KobunEnd,TNONE	,0,0}
};

/*****************************************************************************/
