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

  gbc_trans_ctrl.c

  (c) 2000-2017 Benoît Minisini <benoit.minisini@gambas-basic.org>

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2, or (at your option)
  any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
  MA 02110-1301, USA.

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

#define _TRANS_CTRL_C

#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#include "gb_common.h"
#include "gb_error.h"
#include "gbc_compile.h"
#include "gbc_trans.h"
#include "gb_code.h"
#include "gb_limit.h"

/*#define DEBUG*/
/*#define DEBUG_GOTO*/

#define BEGIN_NO_BREAK \
{ \
	bool nobreak = JOB->nobreak; \
	JOB->nobreak = TRUE;

#define END_NO_BREAK \
	JOB->nobreak = nobreak; \
}

#if defined(__GNUC__) && __GNUC__ == 8
static volatile uchar ctrl_level; // Workaround a gcc compiler optimization bug
#else
static uchar ctrl_level;
#endif
static int ctrl_id;
static int ctrl_local;

static TRANS_CTRL ctrl_data[MAX_CTRL_LEVEL];

static TRANS_CTRL *current_ctrl = NULL;

static TRANS_GOTO *goto_info;
static TRANS_LABEL *label_info;
static short *ctrl_parent;

static ushort *_relocation = NULL;

static bool _indirect_goto;

static TYPE _with_info[MAX_CTRL_LEVEL];
static TYPE *_with_current;
static uchar _with_level;

typedef
	struct {
		ushort src;
		ushort dst;
	}
	JUMP;

static JUMP *_jumps = NULL;

static void jump_length(ushort src, ushort dst, bool can_optimize)
{
	JUMP *jump;

	CODE_jump_length(src, dst);

	if (can_optimize)
	{
		jump = ARRAY_add(&_jumps);
		jump->src = src;
		jump->dst = dst;
	}
}


static void control_set_value(int value)
{
	if (ctrl_level <= 0)
		return;

	current_ctrl->value = value;
}


static int control_get_value(void)
{
	if (ctrl_level <= 0)
		return 0;

	return current_ctrl->value;
}


static void control_add_relocation()
{
	if (!_relocation)
		ARRAY_create(&_relocation);
	
	*((ushort *)ARRAY_add(&_relocation)) = CODE_get_current_pos();
}

static void control_add_pos(ushort **tab_pos, ushort pos)
{
	if (!(*tab_pos))
		ARRAY_create(tab_pos);

	*((ushort *)ARRAY_add(tab_pos)) = pos;
}


static void control_add_current_pos(void)
{
	control_add_pos(&current_ctrl->pos, CODE_get_current_pos());
}

static void control_add_current_pos_break()
{
	control_add_pos(&current_ctrl->pos_break, CODE_get_current_pos());
}

static void control_add_current_pos_continue()
{
	control_add_pos(&current_ctrl->pos_continue, CODE_get_current_pos());
}

static void control_add_this_pos(ushort pos)
{
	control_add_pos(&current_ctrl->pos, pos);
}

static void control_add_this_pos_continue(ushort pos)
{
	control_add_pos(&current_ctrl->pos_continue, pos);
}


static void control_jump_each_pos_with(ushort *tab_pos)
{
	int i;

	if (!tab_pos)
		return;

	for (i = 0; i < ARRAY_count(tab_pos); i++)
		jump_length(tab_pos[i], CODE_get_current_pos(), TRUE);
}


static void control_jump_each_pos(void)
{
	control_jump_each_pos_with(current_ctrl->pos);
}


static TRANS_CTRL *control_get_inner(void)
{
	int level = ctrl_level;
	TRANS_CTRL *ctrl_look;

	for(;;)
	{
		if (!level)
			return NULL;

		level--;
		ctrl_look = &ctrl_data[level];

		if ((ctrl_look->type == RS_DO)
				|| (ctrl_look->type == RS_WHILE)
				|| (ctrl_look->type == RS_REPEAT)
				|| (ctrl_look->type == RS_FOR)
				|| (ctrl_look->type == RS_EACH))
			return ctrl_look;
	}
}


static TRANS_CTRL *control_get_inner_with(void)
{
	int level = ctrl_level;
	TRANS_CTRL *ctrl_look;

	for(;;)
	{
		if (!level)
			return NULL;

		level--;
		ctrl_look = &ctrl_data[level];

		if (ctrl_look->type == RS_WITH)
			return ctrl_look;
	}
}


static void add_goto(int index, int mode)
{
	TRANS_GOTO *info;

	if (goto_info == NULL)
		ARRAY_create(&goto_info);

	info = ARRAY_add(&goto_info);
	info->index = index;
	info->pos = CODE_get_current_pos();
	info->ctrl_id = (ctrl_level == 0) ? 0 : current_ctrl->id;
	info->line = JOB->line;
	info->gosub = mode == RS_GOSUB;
	info->on_goto = mode == RS_NONE;

	#ifdef DEBUG_GOTO
		printf("add_goto: ctrl_id = %d (%ld)\n", info->ctrl_id, ARRAY_count(goto_info));
	#endif

	if (mode == RS_GOSUB)
	{
		//control_add_relocation();
		CODE_gosub(ctrl_local);
	}
	else if (mode == RS_GOTO)
		CODE_jump();
	else
		CODE_nop();
}


static void control_enter(int type)
{
	short *parent;

	if (ctrl_level >= MAX_CTRL_LEVEL)
		THROW("Too many nested control structures.");

	ctrl_id++;

	current_ctrl = &ctrl_data[ctrl_level];

	current_ctrl->type = type;
	current_ctrl->value = 0;
	current_ctrl->pos = NULL;
	current_ctrl->state = 0;
	current_ctrl->local = ctrl_local;
	current_ctrl->id = ctrl_id;
	current_ctrl->loop_var = -1;

	parent = ARRAY_add(&ctrl_parent);

	if (ctrl_level == 0)
		*parent = 0;
	else
		*parent = ctrl_data[ctrl_level - 1].id;

	switch (type)
	{
		case RS_FOR:
		case RS_EACH:
			ctrl_local += 2;
			break;

		case RS_SELECT:
		case RS_WITH:
			ctrl_local += 1;
			break;
	}

	JOB->func->nctrl = Max(JOB->func->nctrl, ctrl_local);
	if ((JOB->func->nctrl + JOB->func->nlocal) > MAX_LOCAL_SYMBOL)
		THROW("Too many local variables");

	ctrl_level++;
}


static void control_leave(void)
{
	control_jump_each_pos_with(current_ctrl->pos_break);

	ARRAY_delete(&current_ctrl->pos);
	ARRAY_delete(&current_ctrl->pos_break);
	ARRAY_delete(&current_ctrl->pos_continue);

	ctrl_local = current_ctrl->local;

	ctrl_level--;

	if (ctrl_level > 0)
		current_ctrl = &ctrl_data[ctrl_level - 1];
	else
		current_ctrl = NULL;
}


static void control_check(int type, const char *msg1, const char *msg2)
{
	if (ctrl_level <= 0)
		THROW(msg1);

	if (current_ctrl->type != type)
		THROW(E_UNEXPECTED, msg2);
}

static void control_check_two(int type1, int type2, const char *msg1, const char *msg2)
{
	if (ctrl_level <= 0)
		THROW(msg1);

	if (current_ctrl->type != type1 && current_ctrl->type != type2)
		THROW(E_UNEXPECTED, msg2);
}

static void control_check_loop_var(short var)
{
	int i;

	for (i = 0; i < (ctrl_level - 1); i++)
	{
		if (ctrl_data[i].loop_var == var)
			THROW("Loop variable already in use");
	}

	current_ctrl->loop_var = var;
}

static void check_try(const char *name)
{
	if (TRANS_in_try)
	{
		const char *keyword = TRANS_in_try == RS_TRY ? "TRY" : "ASSERT";
		TRANS_in_try = RS_NONE;
		if (strcmp(name, keyword))
			THROW("Cannot use &1 with &2", keyword, name);
		else
			THROW("Cannot use &1 twice", keyword);
	}
}

void TRANS_control_init(void)
{
	ctrl_level = 0;
	ctrl_id = 0;
	current_ctrl = NULL;

	goto_info = NULL;

	label_info = NULL;

	ctrl_local = 0; //JOB->func->nlocal;
	JOB->func->nctrl = 0;

	ARRAY_create(&ctrl_parent);
	ARRAY_create(&_jumps);

	_indirect_goto = FALSE;
	
	_with_level = 0;
	_with_current = NULL;
}


void TRANS_control_exit(void)
{
	int i, j;
	CLASS_SYMBOL *sym;
	int line;
	TRANS_LABEL *label;
	int nlabel;
	short id;
	ushort *pcode;
	JUMP *jump;

	// Relocate locals
	
	if (_relocation)
	{
		for (i = 0; i < ARRAY_count(_relocation); i++)
		{
			pcode = &JOB->func->code[_relocation[i]];
			if (PCODE_is_breakpoint(*pcode))
				pcode++;
			
			*pcode += JOB->func->nlocal;
			
			/*fprintf(stderr, "%04d : %04X --> ", _relocation[i], pcode);
			pcode = (pcode & 0xFF00) | ((pcode & 0xFF) + JOB->func->nlocal);
			fprintf(stderr, "%04X\n", pcode);
			JOB->func->code[_relocation[i]] = pcode;*/
		}
		
		ARRAY_delete(&_relocation);
	}
	
	// Resolve GOTOs

	if (goto_info)
	{
		line = JOB->line;

		for (i = 0; i < ARRAY_count(goto_info); i++)
		{
			JOB->line = goto_info[i].line;
			/*printf("%d\n", JOB->line);*/

			sym = CLASS_get_symbol(JOB->class, goto_info[i].index);

			if (TYPE_get_kind(sym->local.type) != TK_LABEL)
				THROW("Label '&1' not declared", TABLE_get_symbol_name(JOB->class->table, goto_info[i].index));

			label = &label_info[sym->local.value];

			id = goto_info[i].ctrl_id;

			if (goto_info[i].gosub)
			{
				if (label->ctrl_id)
					THROW("Forbidden GOSUB");
			}
			else
			{
				for(;;)
				{
					if (label->ctrl_id == id)
						break;

					if (id == 0)
						THROW("Forbidden GOTO");

					#ifdef DEBUG_GOTO
						printf("id = %d ctrl_parent[id - 1] = %d (%ld)\n", id, ctrl_parent[id - 1], ARRAY_count(ctrl_parent));
					#endif
					id = ctrl_parent[id - 1];
				}
			}

			jump_length(goto_info[i].pos, label->pos, !goto_info[i].on_goto);
		}

		JOB->line = line;
	}

	// Indirect Goto or Gosub

	if (label_info && _indirect_goto)
	{
		nlabel = ARRAY_count(label_info);

		JOB->func->nlabel = nlabel;

		ALLOC(&JOB->func->indirect_goto, sizeof(ushort) * nlabel);

		for (i = 0; i < nlabel; i++)
		{
			label = &label_info[i];
			JOB->func->indirect_goto[i] = label->ctrl_id ? -1 : label->pos;
		}
	}

	// Optimize jumps

	for (i = 0; i < ARRAY_count(_jumps); i++)
	{
		jump = &_jumps[i];

		for(j = 1; j <= 4; j++) // avoid infinite loop
		{
			if (jump->dst >= JOB->func->ncode)
				break;
			pcode = &JOB->func->code[jump->dst];

			if (PCODE_is_breakpoint(*pcode))
			{
				pcode++;
				jump->dst++;
			}

			if (!PCODE_is_jump(*pcode))
				break;

			jump->dst += ((short *)pcode)[1] + 2;
			CODE_jump_length(jump->src, jump->dst);
		}
	}

	// Remove previously declared labels

	if (label_info)
	{
		for (i = 0; i < ARRAY_count(label_info); i++)
		{
			sym = CLASS_get_symbol(JOB->class, label_info[i].index);
			TYPE_clear(&sym->local.type);
		}
	}

	ARRAY_delete(&goto_info);
	ARRAY_delete(&ctrl_parent);
	ARRAY_delete(&label_info);
	ARRAY_delete(&_jumps);

	// Detect structures still opened

	if (ctrl_level == 0) return;

	switch (ctrl_data[ctrl_level - 1].type)
	{
		case RS_IF:
			THROW(E_MISSING, "ENDIF");
		case RS_FOR:
		case RS_EACH:
			THROW(E_MISSING, "NEXT");
		case RS_DO:
			THROW(E_MISSING, "LOOP");
		case RS_REPEAT:
			THROW(E_MISSING, "UNTIL");
		case RS_WHILE:
			THROW(E_MISSING, "WEND");
		case RS_SELECT:
			THROW(E_MISSING, "END SELECT");
		case RS_WITH:
			THROW(E_MISSING, "END WITH");
	}
}

static ushort trans_jump_if(bool if_true, bool fast)
{
	ushort pos;
	
	if (CODE_check_jump_not())
	{
		if_true = !if_true;
		fast = FALSE;
	}
	
	pos = CODE_get_current_pos();

	CODE_jump_if(if_true, fast);

	return pos;
}

/*static void trans_endif(void)
{
	if (current_ctrl->state == 0)
		jump_length(control_get_value(), CODE_get_current_pos());

	control_jump_each_pos();
}*/

static void trans_else(void)
{
	BEGIN_NO_BREAK
	{
		control_add_current_pos_break();
		CODE_jump();
	}
	END_NO_BREAK

	control_jump_each_pos_with(current_ctrl->pos_continue);
	ARRAY_delete(&current_ctrl->pos_continue);
	//jump_length(control_get_value(), CODE_get_current_pos());

	current_ctrl->state = 1;
}

static bool trans_boolean_expr(void)
{
	TRANS_expression(FALSE);
	return TYPE_is_boolean(TRANS_get_last_type());
}

static void trans_if(void)
{
	bool fast;

	fast = trans_boolean_expr();

	if (PATTERN_is(*JOB->current, RS_AND))
	{
		for(;;)
		{
			control_add_this_pos_continue(trans_jump_if(FALSE, fast));
			
			if (!PATTERN_is(*JOB->current, RS_AND))
				break;
			
			JOB->current++;
			
			TRANS_want(RS_IF, "AND IF");
			
			fast = trans_boolean_expr();
		}
	}
	else if (PATTERN_is(*JOB->current, RS_OR))
	{
		for(;;)
		{
			control_add_this_pos(trans_jump_if(TRUE, fast));
			
			if (!PATTERN_is(*JOB->current, RS_OR))
				break;
			
			JOB->current++;
			
			TRANS_want(RS_IF, "OR IF");
			
			fast = trans_boolean_expr();
		}
		
		control_add_current_pos_continue();
		CODE_jump();
	}
	else
	{
		control_add_this_pos_continue(trans_jump_if(FALSE, fast));
	}
	
	if (PATTERN_is(*JOB->current, RS_THEN))
		JOB->current++;
	else if (!PATTERN_is_newline(*JOB->current))
		THROW(E_EXPECTED, "THEN");
	
	control_jump_each_pos();
	ARRAY_delete(&current_ctrl->pos);
}

static void trans_else_if(void)
{
	trans_else();
	current_ctrl->state = 0;
	trans_if();
}


void TRANS_if(void)
{
	PATTERN *look;
	bool has_else;
	
	control_enter(RS_IF);

	trans_if();

	if (PATTERN_is_newline(*JOB->current))
		return;

	has_else = FALSE;
	look = JOB->current;
	for(;;)
	{
		look++;
		if (PATTERN_is_newline(*look))
			break;
		if (PATTERN_is(*look, RS_ELSE))
		{
			has_else = TRUE;
			*look = PATTERN_make(RT_NEWLINE, 0);
			break;
		}
	}
	
	TRANS_statement();
	
	if (has_else)
	{
		JOB->current++;
		trans_else();
		TRANS_statement();
	}
	
	TRANS_endif();
}


void TRANS_else(void)
{
	control_check(RS_IF, "ELSE without IF", "ELSE");

	if (current_ctrl->state)
		THROW(E_UNEXPECTED, "ELSE");

	if (TRANS_is(RS_IF))
		trans_else_if();
	else
		trans_else();
}


void TRANS_endif(void)
{
	control_check(RS_IF, "ENDIF without IF", "ENDIF");
	control_jump_each_pos_with(current_ctrl->pos_continue);
	control_leave();
}


static void TRANS_goto_gosub(bool gosub)
{
	CLASS_SYMBOL *sym;
	int index;

	check_try(gosub ? "GOSUB" : "GOTO");
	
	if (PATTERN_is_identifier(*JOB->current))
	{
		index = PATTERN_index(*JOB->current);

		sym = CLASS_get_symbol(JOB->class, index);

		if (TYPE_get_kind(sym->local.type) == TK_LABEL)
		{
			JOB->current++;
			add_goto(index, gosub ? RS_GOSUB : RS_GOTO);
			return;
		}
	}

	_indirect_goto = TRUE;
	TRANS_expression(FALSE);
	CODE_on(0);
	if (gosub)
		CODE_gosub(0);
	else
		CODE_jump();

}

void TRANS_goto(void)
{
	TRANS_goto_gosub(FALSE);
}


void TRANS_gosub(void)
{
	TRANS_goto_gosub(TRUE);
}


void TRANS_on_goto_gosub(void)
{
	bool gosub;
	int pos, n;
	int index;
	
	TRANS_expression(FALSE);
	
	if (TRANS_is(RS_GOTO))
		gosub = FALSE;
	else if (TRANS_is(RS_GOSUB))
		gosub = TRUE;
	else
		THROW(E_SYNTAX);

	check_try(gosub ? "ON GOSUB" : "ON GOTO");
	
	pos = CODE_get_current_pos();
	CODE_nop();
	
	for (n = 1;; n++)
	{
		if (!PATTERN_is_identifier(*JOB->current))
			THROW(E_SYNTAX);
		
		index = PATTERN_index(*JOB->current);
		JOB->current++;

		add_goto(index, RS_NONE);
		
		if (!TRANS_is(RS_COMMA))
			break;
		
		if (n == 127)
			THROW("Too many labels");

		TRANS_newline();
	}
	
	pos = CODE_set_current_pos(pos);
	CODE_on(n);
	CODE_set_current_pos(pos);
	
	if (gosub)
	{
		//control_add_relocation();
		CODE_gosub(ctrl_local);
	}
	else
		CODE_jump();
}


void TRANS_do(int type)
{
	bool is_until;
	bool fast;

	control_enter(type);
	control_set_value(CODE_get_current_pos());

	if (type == RS_REPEAT)
		return;

	is_until = PATTERN_is(*JOB->current, RS_UNTIL);

	if (PATTERN_is(*JOB->current, RS_WHILE)
			|| is_until)
	{
		JOB->current++;

		fast = trans_boolean_expr();
		control_add_this_pos(trans_jump_if(is_until, fast));
	}
}


void TRANS_loop(int type)
{
	ushort pos;
	bool is_until;
	bool fast;

	if (type == RS_LOOP)
		control_check(RS_DO, "LOOP without DO", "LOOP");
	else if (type == RS_UNTIL)
		control_check(RS_REPEAT, "UNTIL without REPEAT", "UNTIL");
	else if (type == RS_WEND)
		control_check(RS_WHILE, "WEND without WHILE", "WEND");

	control_jump_each_pos_with(current_ctrl->pos_continue);

	is_until = PATTERN_is(*JOB->current, RS_UNTIL);

	if ((type != RS_WEND) && (PATTERN_is(*JOB->current, RS_WHILE) || is_until))
	{
		JOB->current++;

		fast = trans_boolean_expr();
		pos = trans_jump_if(!is_until, fast);
		
		jump_length(pos, control_get_value(), TRUE);
	}
	else
	{
		pos = CODE_get_current_pos();
		CODE_jump();
		jump_length(pos, control_get_value(), TRUE);
	}

	control_jump_each_pos();
	control_leave();
}


static void trans_select_break(bool do_not_add_pos)
{
	BEGIN_NO_BREAK
	{
		if (current_ctrl->value)
		{
			if (!do_not_add_pos)
			{
				control_add_current_pos();
				CODE_jump();
			}

			jump_length(current_ctrl->value, CODE_get_current_pos(), TRUE);
		}
	}
	END_NO_BREAK
}


void TRANS_select(void)
{
	control_enter(RS_SELECT);

	if (PATTERN_is(*JOB->current, RS_CASE))
		JOB->current++;

	TRANS_expression(FALSE);
	
	control_add_relocation();
	CODE_pop_ctrl(current_ctrl->local);
}


void TRANS_case(void)
{
	int i;
	short pos;
	short local;
	bool like;

	control_check(RS_SELECT, "CASE without SELECT", "CASE");
	
	if (current_ctrl->state)
		THROW("Default case must be the last one");

	trans_select_break(FALSE);

	local = current_ctrl->local;

	control_enter(RS_CASE);
	
	like = TRANS_is(RS_LIKE);
	
	for(i = 0; ; i++)
	{
		if (i > MAX_CASE_EXPR)
			THROW("Too many expressions in CASE");

		if (PATTERN_is_newline_end(*JOB->current))
			THROW("Unexpected end of line");
		
		/*CODE_dup();
		TRANS_expression(FALSE);
		CODE_op(C_EQ, 2);*/
		
		if (like)
		{
			control_add_relocation();
			CODE_push_local(local);
			TRANS_expression(FALSE);
			CODE_op(C_LIKE, 0, 2, TRUE);
		}
		else if (TRANS_is(RS_TO))
		{
			control_add_relocation();
			CODE_push_local(local);
			TRANS_expression(FALSE);
			CODE_op(C_LE, 0, 2, TRUE);
		}
		else
		{
			TRANS_expression(FALSE);

			if (TRANS_is(RS_TO))
			{
				control_add_relocation();
				CODE_push_local(local);
				CODE_op(C_LE, 0, 2, TRUE);
				control_add_relocation();
				CODE_push_local(local);
				TRANS_expression(FALSE);
				CODE_op(C_LE, 0, 2, TRUE);
				CODE_op(C_AND, 0, 2, TRUE);
			}
			else
			{
				control_add_relocation();
				CODE_push_local(local);
				CODE_op(C_EQ, 0, 2, TRUE);
			}
		}

		if (!PATTERN_is(*JOB->current, RS_COMMA))
		{
			//TRANS_ignore(RS_THEN);
			pos = CODE_get_current_pos();
			CODE_jump_if(FALSE, FALSE);
			break;
		}

		control_add_current_pos();
		CODE_jump_if(TRUE, FALSE);

		JOB->current++;
		TRANS_newline();
	}

	control_jump_each_pos();
	control_leave();

	current_ctrl->value = pos;
}


void TRANS_default(void)
{
	control_check(RS_SELECT, "DEFAULT without SELECT", "DEFAULT");

	if (current_ctrl->state)
		THROW("Default case already defined");

	trans_select_break(FALSE);

	current_ctrl->value = 0; /*CODE_get_current_pos();*/
	current_ctrl->state = 1;
}


void TRANS_end_select(void)
{
	control_check(RS_SELECT, "END SELECT without SELECT", "END SELECT");

	/*
	if (current_ctrl->value)
		jump_length(current_ctrl->value, CODE_get_current_pos());
	*/

	trans_select_break(TRUE);

	control_jump_each_pos();

	control_leave();
}


void TRANS_break(void)
{
	TRANS_CTRL *ctrl_inner = control_get_inner();

	check_try("BREAK");

	if (!ctrl_inner)
		THROW(E_UNEXPECTED, "BREAK");

	control_add_pos(&ctrl_inner->pos_break, CODE_get_current_pos());
	CODE_jump();
}


void TRANS_continue(void)
{
	TRANS_CTRL *ctrl_inner = control_get_inner();

	check_try("CONTINUE");
	
	if (!ctrl_inner)
		THROW(E_UNEXPECTED, "CONTINUE");

	control_add_pos(&ctrl_inner->pos_continue, CODE_get_current_pos());
	CODE_jump();
}


void TRANS_return(void)
{
	if (FUNCTION_is_procedure(JOB->func))
	{
		if (!(PATTERN_is_newline(*JOB->current) || PATTERN_is_end(*JOB->current)))
			THROW("Return value datatype not specified in function declaration");
	}

	if (PATTERN_is_newline(*JOB->current) || PATTERN_is_end(*JOB->current))
		CODE_return(0);
	else
	{
		TRANS_expression(FALSE);
		CODE_return(1);
	}
}


void TRANS_for(void)
{
	int local;
	bool downto = FALSE;

	control_enter(RS_FOR);

	local = TRANS_loop_local(FALSE);
	
	TRANS_want(RS_EQUAL, "=");
	
	TRANS_expression(FALSE);
	
	CODE_pop_local_noref(local);

	control_check_loop_var(local);

	if (TRANS_is(RS_DOWNTO))
		downto = TRUE;
	else
		TRANS_want(RS_TO, "TO");

	TRANS_expression(FALSE);

	if (PATTERN_is(*JOB->current, RS_STEP))
	{
		JOB->current++;
		TRANS_expression(FALSE);
		if (downto)
			CODE_op(C_NEG, 0, 1, TRUE);
	}
	else
	{
		CODE_push_number(downto ? -1 : 1);
	}

	if (!PATTERN_is_newline(*JOB->current))
		THROW(E_UNEXPECTED, READ_get_pattern(JOB->current));

	control_add_relocation();
	CODE_jump_first(current_ctrl->local);

	control_set_value(CODE_get_current_pos());

	control_add_current_pos();
	CODE_jump_next();
	
	CODE_pop_local_noref(local);
}


void TRANS_for_each(void)
{
	PATTERN *iterator = JOB->current;
	PATTERN *save;
	int local;
	bool var = TRUE;

	while (!PATTERN_is(*JOB->current, RS_IN))
	{
		if (PATTERN_is_newline_end(*JOB->current))
		{
			JOB->current = iterator;
			var = FALSE;
			break;
		}
		JOB->current++;
	}

	control_enter(RS_EACH);

	if (var)
		JOB->current++;

	TRANS_expression(FALSE);

	/*CODE_pop_ctrl(current_ctrl->local);*/

	control_add_relocation();
	CODE_first(current_ctrl->local);

	control_set_value(CODE_get_current_pos());
	control_add_current_pos();

	if (var)
	{
		CODE_next(FALSE);

		save = JOB->current;
		JOB->current = iterator;

		local = TRANS_loop_local(TRUE);
		CODE_pop_local(local);
		
		//TRANS_reference();

		TRANS_want(RS_IN, "IN");

		JOB->current = save;
	}
	else
		CODE_next(TRUE);

	return;
}


void TRANS_next(void)
{
	ushort pos;

	control_check_two(RS_FOR, RS_EACH, "NEXT without FOR", "NEXT");

	/*
	if (current_ctrl->type == RS_FOR)
	{
		control_jump_each_pos_with(current_ctrl->pos_continue);

		pos = CODE_get_current_pos();
		CODE_jump();
		jump_length(pos, control_get_value());

		control_jump_each_pos();
		control_leave();
	}
	else
	{
	*/
	control_jump_each_pos_with(current_ctrl->pos_continue);

	pos = CODE_get_current_pos();
	CODE_jump();
	jump_length(pos, control_get_value(), TRUE);

	control_jump_each_pos();
	control_leave();
}


void TRANS_assert(void)
{
	ushort pos;
	
	check_try("ASSERT");

	if (!JOB->debug || JOB->exec)
		CODE_disable();

	TRANS_in_try = RS_ASSERT;
	
	TRANS_expression(FALSE);
	
	if (PATTERN_is(*JOB->current, RS_PRINT) || PATTERN_is(*JOB->current, RS_ERROR))
	{
		CODE_dup();
		pos = CODE_get_current_pos();
		CODE_jump_if(TRUE, FALSE);
	
		TRANS_statement();
	
		jump_length(pos, CODE_get_current_pos(), TRUE);
	}

	TRANS_subr(TS_SUBR_DEBUG, 1);
	CODE_drop();

	TRANS_in_try = RS_NONE;
	
	if (!JOB->debug || JOB->exec)
		CODE_enable();
}


void TRANS_try(void)
{
	ushort pos;

	check_try("TRY");

	pos = CODE_get_current_pos();
	CODE_try();

	TRANS_in_try = RS_TRY;
	TRANS_statement();
	TRANS_in_try = RS_NONE;

	jump_length(pos, CODE_get_current_pos(), TRUE);
	CODE_end_try();
}


void TRANS_finally(void)
{
	ushort pos = CODE_get_current_pos();

	if ((JOB->func->finally != 0)
			|| (JOB->func->catch != 0)
			|| (pos == 0))
		THROW(E_UNEXPECTED, "FINALLY");

	JOB->func->finally = pos;
}


void TRANS_catch(void)
{
	ushort pos = CODE_get_current_pos();

	if ((JOB->func->catch != 0)
			|| (pos == 0))
		THROW(E_UNEXPECTED, "CATCH");

	CODE_catch();

	JOB->func->catch = CODE_get_current_pos();
}


void TRANS_declare_label(void)
{
	CLASS_SYMBOL *sym;
	int sym_index;
	int count;

	if (label_info == NULL)
		ARRAY_create(&label_info);

	count = ARRAY_count(label_info);
	if (count >= MAX_LABEL)
		THROW("Too many labels");

	ARRAY_add(&label_info);

	sym_index = PATTERN_index(*JOB->current);
	JOB->current++;

	sym = CLASS_declare(JOB->class, sym_index, TK_LABEL, FALSE);

	sym->local.type = TYPE_make(T_VOID, -1, TK_LABEL);
	sym->local.value = count;

	JOB->current++;
}


void TRANS_label(void)
{
	CLASS_SYMBOL *sym;
	int sym_index;
	TRANS_LABEL *label;

	sym_index = PATTERN_index(*JOB->current);
	JOB->current++;

	sym = CLASS_get_symbol(JOB->class, sym_index);
	label = &label_info[sym->local.value];

	label->index = sym_index;
	label->pos = CODE_get_current_pos();
	label->ctrl_id = ctrl_level ? current_ctrl->id : 0;

	#ifdef DEBUG_GOTO
	printf("TRANS_label: %.*s ctrl_id = %d\n", sym->symbol.len, sym->symbol.name, label->ctrl_id);
	#endif

	/* on saute le ':' */
	JOB->current++;
}


void TRANS_with(void)
{
	if (!TRANS_affectation(TRUE))
		TRANS_expression(FALSE);

	if (_with_level >= MAX_CTRL_LEVEL)
		THROW("Too many nested WITH");
	_with_info[_with_level++] = TRANS_get_last_type();
	
	control_enter(RS_WITH);

	control_add_relocation();
	CODE_pop_ctrl(current_ctrl->local);
}

TYPE TRANS_use_with(void)
{
	TRANS_CTRL *ctrl_inner = control_get_inner_with();

	if (ctrl_inner == NULL)
		THROW("Syntax error. Point syntax used outside of WITH / END WITH");

	control_add_relocation();
	CODE_push_local(ctrl_inner->local);
	
	return _with_info[_with_level - 1];
}


void TRANS_end_with(void)
{
	control_check(RS_WITH, "END WITH without WITH", "END WITH");

	control_leave();
	_with_level--;
}


void TRANS_raise(void)
{
	CLASS_SYMBOL *sym;
	int index;
	int np;

	if (!PATTERN_is_identifier(*JOB->current))
		THROW("Syntax error in event name");

	index = PATTERN_index(*JOB->current);
	sym = CLASS_get_symbol(JOB->class, index);

	JOB->current++;

	if (TYPE_get_kind(sym->global.type) == TK_EVENT)
	{
		if (TYPE_is_static(JOB->func->type))
			THROW("Cannot raise events in static function");

		CODE_push_event(sym->global.value);
	}
	else
	{
		CODE_push_unknown_event(CLASS_add_unknown(JOB->class, index));
	}

	np = 0;

	if (TRANS_is(RS_LBRA))
	{
		for (;;)
		{
			if (TRANS_is(RS_RBRA))
				break;

			if (np > 0)
				TRANS_want(RS_COMMA, "comma");

			if (np >= MAX_PARAM_FUNC)
				THROW("Too many arguments");

			TRANS_expression(FALSE);
			np++;
		}
	}

	CODE_call(np);

	if (!TRANS_in_assignment)
		CODE_drop();
}

TRANS_LABEL *TRANS_get_label_info(int value)
{
	return &label_info[value];
}
