/****************************************************************
 *								*
 *	Copyright 2001, 2012 Fidelity Information Services, Inc	*
 *								*
 *	This source code contains the intellectual property	*
 *	of its copyright holder(s), and is made available	*
 *	under a license.  If you do not know the terms of	*
 *	the license, please stop and do not read further.	*
 *								*
 ****************************************************************/

#include "mdef.h"
#include "compiler.h"
#include "mdq.h"
#include "opcode.h"
#include "toktyp.h"
#include "mmemory.h"
#include "advancewindow.h"
#include "cmd.h"
#include "lv_val.h"

error_def(ERR_EQUAL);
error_def(ERR_FOROFLOW);
error_def(ERR_MAXFORARGS);
error_def(ERR_SPOREOL);

/* The following macro checks to see if the evaluation of control variable components has done
 * anything that might have expose us to a messed up the control variable context. We only
 * have a problem when the control variable is subscripted, because if an extrinsic rearranges
 * the array - a KILL will do it - the op_putindx we did initially might be pointing into
 * never-neverland and slamming a value into it would definately not be a healthly thing.
 * Without indirection we know at compile time whether or not the control variable is subscripted
 * but with indirection we only know at run-time; we tried some contortions to skip the refresh if
 * it's not needed but lost the battle with the compiler's tendency to lose reference with a scope
 * that's not short - OC_PASSTHRU is suppose to give it a clue but having two of those in a row
 * seems not to work.
 */
#define	DEAL_WITH_DANGER(LVL, CNTL_VAR, VAL)								\
{													\
	triple *Ref;											\
													\
	if ((TRUE_WITH_INDX == TAREF1(for_temps, for_stack_level)))					\
	{												\
		Ref = newtriple(OC_RFRSHINDX); 								\
		Ref->operand[0] = put_ilit(LVL); 							\
		Ref->operand[1] = put_ilit(1);								\
		CNTL_VAR = put_tref(Ref);								\
		newtriple(OC_PASSTHRU)->operand[0] = CNTL_VAR;	/* warn off optimizer */		\
	}												\
	Ref = newtriple(OC_STO);									\
	Ref->operand[0] = CNTL_VAR;									\
	Ref->operand[1] = VAL;										\
}

/* the macro below pushes the compiler FOR stack - the FOR_POP is in compiler.h 'cause stx_error uses it
 * there are actually two stacks - one for code references and one for temps flags; the code reference
 * one, for_stack, uses for_stack_ptr; the for_temps doesn't have its own global index, but instead uses
 * a local variable calculated from the relationship between the for_stack and for_stack_ptr
 */
#define	FOR_PUSH()											\
{													\
	int	Level;											\
													\
	Level = ((++(TREF(for_stack_ptr))) - (oprtype **)TADR(for_stack));				\
	if (MAX_FOR_STACK > Level)									\
	{												\
		assert(TREF(for_stack_ptr) > (oprtype **)TADR(for_stack));				\
		*(TREF(for_stack_ptr)) = NULL;								\
		TAREF1(for_temps, Level) = TAREF1(for_temps, Level - 1) ? TRUE : FALSE;			\
	} else												\
	{												\
		--(TREF(for_stack_ptr));								\
		stx_error(ERR_FOROFLOW, 1, (MAX_FOR_STACK - 1));					\
		FOR_POP(BLOWN_FOR);									\
		return FALSE;										\
	}												\
}

/* the macro below tucks a code reference into the for_stack so a FOR that's done can move on correctly when skipped */
#define	SAVE_FOR_OVER_ADDR()											\
{														\
	assert(TREF(for_stack_ptr) >= (oprtype **)TADR(for_stack));						\
	assert(TREF(for_stack_ptr) < (oprtype **)(TADR(for_stack) + (MAX_FOR_STACK * SIZEOF(oprtype **))));	\
	if (NULL == *(TREF(for_stack_ptr)))									\
		*(TREF(for_stack_ptr)) = (oprtype *)mcalloc(SIZEOF(oprtype));					\
	tnxtarg(*(TREF(for_stack_ptr)));									\
}

int m_for(void)
{
	unsigned int	arg_cnt, arg_index, for_stack_level;
	oprtype		arg_eval_addr[MAX_FORARGS], increment[MAX_FORARGS], terminate[MAX_FORARGS],
			arg_next_addr, arg_value, dummy, control_variable,
			*iteration_start_addr, iteration_start_addr_indr, *not_even_once_addr;
	triple		*eval_next_addr[MAX_FORARGS], *control_ref,
			*forchk1opc, forpos_in_chain, *init_ref, *ref, *step_ref, *term_ref, *var_ref;
	DCL_THREADGBL_ACCESS;

	SETUP_THREADGBL_ACCESS;
	forpos_in_chain = TREF(pos_in_chain);
	FOR_PUSH();
	if (TK_SPACE == TREF(window_token))
	{	/* "argumentless" form */
		FOR_END_OF_SCOPE(1, dummy);
		ref = newtriple(OC_FORCHK1);
		if (!linetail())
		{
			TREF(pos_in_chain) = forpos_in_chain;
			assert(TREF(source_error_found));
			stx_error(TREF(source_error_found));
			FOR_POP(BLOWN_FOR);
			return FALSE;
		}
		SAVE_FOR_OVER_ADDR();				/* stash address of next op in the for_stack array */
		newtriple(OC_JMP)->operand[0] = put_tjmp(ref);	/* transfer back to just before the begining of the body */
		FOR_POP(GOOD_FOR);				/* and pop the array */
		return TRUE;
	}
	for_stack_level = (TREF(for_stack_ptr) - TADR(for_stack));
	init_ref = newtriple(OC_FORNESTLVL);
	init_ref->operand[0] = put_ilit(for_stack_level);
	if (TK_ATSIGN == TREF(window_token))
	{
		if (!indirection(&control_variable))
		{
			FOR_POP(BLOWN_FOR);
			return FALSE;
		}
		ref = newtriple(OC_INDLVADR);
		ref->operand[0] = control_variable;
		control_variable = put_tref(ref);
		control_ref = NULL;
	} else
	{
		/* The following relies on the fact that lvn() always generates an OC_VAR triple first */
		control_ref = (TREF(curtchain))->exorder.bl;
		if (!lvn(&control_variable, OC_SAVPUTINDX, NULL))
		{
			FOR_POP(BLOWN_FOR);
			return FALSE;
		}
		assert(OC_VAR == control_ref->exorder.fl->opcode);
		assert(MVAR_REF == control_ref->exorder.fl->operand[0].oprclass);
	}
	if (TK_EQUAL != TREF(window_token))
	{
		stx_error(ERR_EQUAL);
		FOR_POP(BLOWN_FOR);
		return FALSE;
	}
	newtriple(OC_PASSTHRU)->operand[0] = control_variable;	/* make sure optimizer doesn't ditch control_variable */
	FOR_END_OF_SCOPE(1, dummy);
	assert((0 < for_stack_level) && (MAX_FOR_STACK >= for_stack_level));
	if ((OC_SAVPUTINDX == control_variable.oprval.tref->opcode) || (OC_INDLVADR == control_variable.oprval.tref->opcode))
		TAREF1(for_temps, for_stack_level) = TRUE_WITH_INDX;	/* most uses treat this as a boolean, but some need more */
	else
		init_ref->opcode = OC_NOOP;
	iteration_start_addr = (oprtype *)mcalloc(SIZEOF(oprtype));
	iteration_start_addr_indr = put_indr(iteration_start_addr);
	arg_next_addr.oprclass = NOCLASS;
	not_even_once_addr = NULL;	/* used to skip processing where the initial control exceeds the termination */
	for (arg_cnt = 0; ; ++arg_cnt)
	{
		if (MAX_FORARGS <= arg_cnt)
		{
			stx_error(ERR_MAXFORARGS);
			FOR_POP(BLOWN_FOR);
			return FALSE;
		}
		assert((TK_COMMA == TREF(window_token)) || (TK_EQUAL == TREF(window_token)));
		advancewindow();
		tnxtarg(&arg_eval_addr[arg_cnt]);		/* put location of this arg eval in arg_eval_addr array */
		if (NULL != not_even_once_addr)
		{
			*not_even_once_addr = arg_eval_addr[arg_cnt];
			not_even_once_addr = NULL;
		}
		if (EXPR_FAIL == expr(&arg_value, MUMPS_EXPR))	/* starting (possibly only) value */
		{
			FOR_POP(BLOWN_FOR);
			return FALSE;
		}
		assert(TRIP_REF == arg_value.oprclass);
		if (TK_COLON != TREF(window_token))
		{	/* list point value? */
			increment[arg_cnt].oprclass = terminate[arg_cnt].oprclass = 0;
			DEAL_WITH_DANGER(for_stack_level, control_variable, arg_value);
		} else
		{	/* stepping value */
			init_ref = newtriple(OC_STOTEMP);		/* tuck it in a temp undisturbed by coming evals */
			init_ref->operand[0] = arg_value;
			newtriple(OC_CONUM)->operand[0] = put_tref(init_ref);	/* make start numeric */
			advancewindow();				/* past the first colon */
			var_ref = (TREF(curtchain))->exorder.bl;
			if (EXPR_FAIL == expr(&increment[arg_cnt], MUMPS_EXPR))	/* pick up step */
			{
				FOR_POP(BLOWN_FOR);
				return FALSE;
			}
			assert(TRIP_REF == increment[arg_cnt].oprclass);
			ref = increment[arg_cnt].oprval.tref;
			if (OC_LIT != var_ref->exorder.fl->opcode)
			{
				if (!TAREF1(for_temps, for_stack_level))
					TAREF1(for_temps, for_stack_level) = TRUE;
				if (OC_VAR == var_ref->exorder.fl->opcode)
				{	/* The above relies on lvn() always generating an OC_VAR triple first - asserted earlier */
					step_ref = newtriple(OC_STOTEMP);
					step_ref->operand[0] = put_tref(ref);
					increment[arg_cnt] = put_tref(step_ref);
				}
			}
			if (TK_COLON != TREF(window_token))
			{
				DEAL_WITH_DANGER(for_stack_level, control_variable, put_tref(init_ref));
				terminate[arg_cnt].oprclass = 0;	/* no termination on iteration for this arg */
			} else
			{
				advancewindow();	/* past the second colon */
				var_ref = (TREF(curtchain))->exorder.bl;
				if (EXPR_FAIL == expr(&terminate[arg_cnt], MUMPS_EXPR))		/* termination control value */
				{
					FOR_POP(BLOWN_FOR);
					return FALSE;
				}
				assert(TRIP_REF == terminate[arg_cnt].oprclass);
				ref = terminate[arg_cnt].oprval.tref;
				if (OC_LIT != ref->opcode)
				{
					if (!TAREF1(for_temps, for_stack_level))
						TAREF1(for_temps, for_stack_level) = TRUE;
					if (OC_VAR == var_ref->exorder.fl->opcode)
					{	/* The above relies on lvn() always generating an OC_VAR triple first */
						term_ref = newtriple(OC_STOTEMP);
						term_ref->operand[0] = put_tref(ref);
						terminate[arg_cnt] = put_tref(term_ref);
					}
				}
				DEAL_WITH_DANGER(for_stack_level, control_variable, put_tref(init_ref));
				term_ref = newtriple(OC_PARAMETER);
				term_ref->operand[0] = terminate[arg_cnt];
				step_ref = newtriple(OC_PARAMETER);
				step_ref->operand[0] = increment[arg_cnt];
				step_ref->operand[1] = put_tref(term_ref);
				ref = newtriple(OC_FORINIT);
				ref->operand[0] = control_variable;
				ref->operand[1] = put_tref(step_ref);
				not_even_once_addr = newtriple(OC_JMPGTR)->operand;
			}
		}
		if ((0 < arg_cnt) || (TK_COMMA == TREF(window_token)))
		{
			if (!TAREF1(for_temps, for_stack_level))
				TAREF1(for_temps, for_stack_level) = TRUE;
			if (NOCLASS == arg_next_addr.oprclass)
				arg_next_addr = put_tref(newtriple(OC_CDADDR));
			(eval_next_addr[arg_cnt] = newtriple(OC_LDADDR))->destination = arg_next_addr;
		}
		if (TK_COMMA != TREF(window_token))
			break;
		newtriple(OC_JMP)->operand[0] = iteration_start_addr_indr;
	}
	forchk1opc = newtriple(OC_FORCHK1);	/* FORCHK1 is a do-nothing routine used by the out-of-band mechanism */
	*iteration_start_addr = put_tjmp(forchk1opc);
	if ((TK_EOL != TREF(window_token)) && (TK_SPACE != TREF(window_token)))
	{
		stx_error(ERR_SPOREOL);
		FOR_POP(BLOWN_FOR);
		return FALSE;
	}
	if (!linetail())
	{
		TREF(pos_in_chain) = forpos_in_chain;
		assert(TREF(source_error_found));
		stx_error(TREF(source_error_found));
		FOR_POP(BLOWN_FOR);
		return FALSE;
	}
	if (not_even_once_addr)		/* if above errors leave FOR remains behind, improper operval.indr explodes OC_JMPGTR */
		 FOR_END_OF_SCOPE(1, *not_even_once_addr);	/* 1 means down a level */
	SAVE_FOR_OVER_ADDR();		/* stash address of next op in the for_stack array */
	if (0 < arg_cnt)
		newtriple(OC_JMPAT)->operand[0] = put_tref(eval_next_addr[0]);
	for (arg_index = 0; arg_index <= arg_cnt; ++arg_index)
	{
		if (0 < arg_cnt)
			tnxtarg(eval_next_addr[arg_index]->operand);
			if (TRUE_WITH_INDX == TAREF1(for_temps, for_stack_level))
			{	/* since it might have moved, before touching the control variable get a fix on it */
				ref = newtriple(OC_RFRSHINDX);
				ref->operand[0] = put_ilit(for_stack_level);
				ref->operand[1] = put_ilit((increment[arg_index].oprclass || terminate[arg_index].oprclass)
					? FALSE : TRUE); /* if increment rather than new value, rfrsh w/ srchindx else putindx */
				control_variable = put_tref(ref);
			} else
			{
				assert(control_ref);
				control_variable = put_mvar(&control_ref->exorder.fl->operand[0].oprval.vref->mvname);
			}
			newtriple(OC_PASSTHRU)->operand[0] = control_variable;	/* warn off optimizer */
		if (terminate[arg_index].oprclass)
		{
			term_ref = newtriple(OC_PARAMETER);
			term_ref->operand[0] = terminate[arg_index];
			step_ref = newtriple(OC_PARAMETER);
			step_ref->operand[0] = increment[arg_index];
			step_ref->operand[1] = put_tref(term_ref);
			init_ref = newtriple(OC_PARAMETER);
			init_ref->operand[0] = control_variable;
			init_ref->operand[1] = put_tref(step_ref);
			ref = newtriple(OC_FORLOOP);
			/* redirects back to forchk1, which is at the beginning of new iteration */
			ref->operand[0] = *iteration_start_addr;
			ref->operand[1] = put_tref(init_ref);
		} else if (increment[arg_index].oprclass)
		{
			step_ref = newtriple(OC_ADD);
			step_ref->operand[0] = control_variable;
			step_ref->operand[1] = increment[arg_index];
			ref = newtriple(OC_STO);
			ref->operand[0] = control_variable;
			ref->operand[1] = put_tref(step_ref);
			newtriple(OC_JMP)->operand[0] = *iteration_start_addr;
		}
		if (arg_index < arg_cnt)	/* go back and evaluate the next argument */
			newtriple(OC_JMP)->operand[0] = arg_eval_addr[arg_index + 1];
	}
	FOR_POP(GOOD_FOR);
	return TRUE;
}
