//***************************************************************************
//
//								ABLSTMT.CPP
//
//***************************************************************************


#include "MW4Headers.hpp"
#ifndef ABLGEN_H
#include "ablgen.h"
#endif

#ifndef ABLERR_H
#include "ablerr.h"
#endif

#ifndef ABLSCAN_H
#include "ablscan.h"
#endif

#ifndef ABLSYMT_H
#include "ablsymt.h"
#endif

#ifndef ABLPARSE_H
#include "ablparse.h"
#endif

#ifndef ABLEXEC_H
#include "ablexec.h"
#endif

#ifndef ABLHEAP_H
#include "ablheap.h"
#endif

#include "ablfuncs.hpp"

namespace ABL
{

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

extern TokenCodeType	curToken;
extern char				tokenString[];
extern char				wordString[];
extern Literal			curLiteral;
extern TokenCodeType	statementStartList[];
extern TokenCodeType	statementEndList[];
extern SymTableNodePtr	symTableDisplay[];
extern long				level;
extern long				FSMLevel;
extern char*			codeBuffer;
extern TypePtr			IntegerTypePtr;
extern TypePtr			RealTypePtr;
extern TypePtr			BooleanTypePtr;
extern TypePtr			CharTypePtr;
extern Type				DummyType;
extern SymTableNodePtr	CurRoutineIdPtr;
extern UserHeapPtr		AblStackHeap;
extern SymTableNodePtr	SymTableDisplay[MAX_NESTING_LEVEL];
extern bool				AssertEnabled;
extern bool				PrintEnabled;
extern bool				StringFunctionsEnabled;
extern bool				Crunch;
extern bool parsingFSM;
extern ABLModulePtr		CurLibrary;

//--------
// GLOBALS
TokenCodeType	FollowSwitchExpressionList[] = {
					TKN_CASE,
					TKN_SEMICOLON,
					TKN_NONE
				};
TokenCodeType	FollowCaseLabelList[] = {
					TKN_COLON,
					TKN_SEMICOLON,
					TKN_NONE
				};
TokenCodeType	CaseLabelStartList[] = {
					TKN_IDENTIFIER,
					TKN_NUMBER,
					TKN_PLUS,
					TKN_MINUS,
					TKN_STRING,
					TKN_NONE
				};

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

void assignmentStatement (SymTableNodePtr varIdPtr) {

	//-----------------------------------
	// Grab the variable we're setting...
	TypePtr varType = variable(varIdPtr);
	ifTokenGetElseError(TKN_EQUAL, ABL_ERR_SYNTAX_MISSING_EQUAL);

	//---------------------------------------------------------
	// Now, get the expression we're setting the variable to...	
	TypePtr exprType = expression();
	
	//----------------------------------------
	// They better be assignment compatible...
	if (!isAssignTypeCompatible(varType, exprType))
		syntaxError(ABL_ERR_SYNTAX_INCOMPATIBLE_ASSIGNMENT);
}

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

void repeatStatement (void) {

	getToken();
	
	if (curToken != TKN_UNTIL) {
		do {
			statement();
			while (curToken == TKN_SEMICOLON)
				getToken();
			if (curToken == TKN_UNTIL)
				break;
		} while (tokenIn(statementStartList));
	}
	
	ifTokenGetElseError(TKN_UNTIL, ABL_ERR_SYNTAX_MISSING_UNTIL);
	
	TypePtr exprType = expression();
	if (exprType != BooleanTypePtr)
		syntaxError(ABL_ERR_SYNTAX_INCOMPATIBLE_TYPES);
}

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

void whileStatement (void) {

	// NEW STYLE, using endwhile keyword...
	getToken();
	char* loopEndLocation = crunchAddressMarker(NULL);
	
	TypePtr exprType = expression();
	if (exprType != BooleanTypePtr)
		syntaxError(ABL_ERR_SYNTAX_INCOMPATIBLE_TYPES);

	//---------------------------------------
	// Let's not use a DO keyword, for now...
	ifTokenGetElseError(TKN_DO, ABL_ERR_SYNTAX_MISSING_DO);

	if (curToken != TKN_END_WHILE)
		do {
			statement();
			while (curToken == TKN_SEMICOLON)
				getToken();
			if (curToken == TKN_END_WHILE)
				break;
		} while (tokenIn(statementStartList));
	
	ifTokenGetElseError(TKN_END_WHILE, ABL_ERR_SYNTAX_MISSING_END_WHILE);

	fixupAddressMarker(loopEndLocation);
}

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

void ifStatement (void) {

	getToken();
	char* falseLocation = crunchAddressMarker(NULL);
	
	TypePtr exprType = expression();
	if (exprType != BooleanTypePtr)
		syntaxError(ABL_ERR_SYNTAX_INCOMPATIBLE_TYPES);
		
	ifTokenGetElseError(TKN_THEN, ABL_ERR_SYNTAX_MISSING_THEN);

	if ((curToken != TKN_END_IF) && (curToken != TKN_ELSE))
		do {
			statement();
			while (curToken == TKN_SEMICOLON)
				getToken();
			if ((curToken == TKN_END_IF) || (curToken == TKN_ELSE))
				break;
		} while (tokenIn(statementStartList));
	
	fixupAddressMarker(falseLocation);
	
	//-----------------------------
	// ELSE branch, if necessary...
	if (curToken == TKN_ELSE) {
		getToken();
		char* ifEndLocation = crunchAddressMarker(NULL);

		if (curToken != TKN_END_IF)
			do {
				statement();
				while (curToken == TKN_SEMICOLON)
					getToken();
				if (curToken == TKN_END_IF)
					break;
			} while (tokenIn(statementStartList));
		
		fixupAddressMarker(ifEndLocation);
	}

	ifTokenGetElseError(TKN_END_IF, ABL_ERR_SYNTAX_MISSING_END_IF);
}

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

void forStatement (void) {

	getToken();
	char* loopEndLocation = crunchAddressMarker(NULL);
	
	TypePtr forType = NULL;
	if (curToken == TKN_IDENTIFIER) {
		SymTableNodePtr forIdPtr = NULL;
		searchAndFindAllSymTables(forIdPtr);
		crunchSymTableNodePtr(forIdPtr);
		if (/*(forIdPtr->level != level) ||*/ (forIdPtr->defn.key != DFN_VAR))
			syntaxError(ABL_ERR_SYNTAX_INVALID_FOR_CONTROL);
		
		forType = baseType((TypePtr)(forIdPtr->typePtr));
		getToken();
		
		//------------------------------------------------------------------
		// If we end up adding a CHAR type, this line needs to be changed...
		if ((forType != IntegerTypePtr) && /*(forType != CharTypePtr) &&*/ (forType->form != FRM_ENUM))
			syntaxError(ABL_ERR_SYNTAX_INCOMPATIBLE_TYPES);
	
		}
	else {
		syntaxError(ABL_ERR_SYNTAX_MISSING_IDENTIFIER);
		forType = &DummyType;
	}
	
	ifTokenGetElseError(TKN_EQUAL, ABL_ERR_SYNTAX_MISSING_EQUAL);
	
	TypePtr exprType = expression();
	if (!isAssignTypeCompatible(forType, exprType))
		syntaxError(ABL_ERR_SYNTAX_INCOMPATIBLE_TYPES);
		
	if (curToken == TKN_TO)
		getToken();
	else
		syntaxError(ABL_ERR_SYNTAX_MISSING_TO);

	exprType = expression();
	if (!isAssignTypeCompatible(forType, exprType))
		syntaxError(ABL_ERR_SYNTAX_INCOMPATIBLE_TYPES);

	//-----------------------------------------
	// For now, let's use the DO keyword...
	ifTokenGetElseError(TKN_DO, ABL_ERR_SYNTAX_MISSING_DO);		

	if (curToken != TKN_END_FOR)
		do {
			statement();
			while (curToken == TKN_SEMICOLON)
				getToken();
			if (curToken == TKN_END_FOR)
				break;
		} while (tokenIn(statementStartList));

	ifTokenGetElseError(TKN_END_FOR, ABL_ERR_SYNTAX_MISSING_END_FOR);

	fixupAddressMarker(loopEndLocation);
}

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

TypePtr caseLabel (CaseItemPtr& caseItemHead, CaseItemPtr& caseItemTail, long& caseLabelCount) {

	CaseItemPtr newCaseItem = (CaseItemPtr)AblStackHeap->Malloc(sizeof(CaseItem));
	if (!newCaseItem)
		ABL_Fatal(0, " ABL: Unable to AblStackHeap->malloc case item ");
	if (caseItemHead) {
		caseItemTail->next = newCaseItem;
		caseItemTail = newCaseItem;
		}
	else
		caseItemHead = caseItemTail = newCaseItem;
	newCaseItem->next = NULL;
	caseLabelCount++;

	TokenCodeType sign = TKN_PLUS;
	bool sawSign = false;
	if ((curToken == TKN_PLUS) || (curToken == TKN_MINUS)) {
		sign = curToken;
		sawSign = true;
		getToken();
	}

	if (curToken == TKN_NUMBER) {
		SymTableNodePtr thisNode = searchSymTable(tokenString, SymTableDisplay[1]);
		if (!thisNode)
			thisNode = enterSymTable(tokenString, &SymTableDisplay[1]);
		crunchSymTableNodePtr(thisNode);
		if (curLiteral.type == LIT_INTEGER)
			newCaseItem->labelValue = (sign == TKN_PLUS) ? curLiteral.value.integer : -curLiteral.value.integer;
		else
			syntaxError(ABL_ERR_SYNTAX_INVALID_CONSTANT);
		return(IntegerTypePtr);
		}
	else if (curToken == TKN_IDENTIFIER) {
		SymTableNodePtr idPtr;
		searchAllSymTables(idPtr);
		crunchSymTableNodePtr(idPtr);
		if (!idPtr) {
			syntaxError(ABL_ERR_SYNTAX_UNDEFINED_IDENTIFIER);
			return(&DummyType);
			}
		else if (idPtr->defn.key != DFN_CONST) {
			syntaxError(ABL_ERR_SYNTAX_NOT_A_CONSTANT_IDENTIFIER);
			return(&DummyType);
			}
		else if (idPtr->typePtr == IntegerTypePtr) {
			newCaseItem->labelValue = (sign == TKN_PLUS ? idPtr->defn.info.constant.value.integer : -idPtr->defn.info.constant.value.integer);
			return(IntegerTypePtr);
			}
		else if (idPtr->typePtr == CharTypePtr) {
			if (sawSign)
				syntaxError(ABL_ERR_SYNTAX_INVALID_CONSTANT);
			newCaseItem->labelValue = idPtr->defn.info.constant.value.character;
			return(CharTypePtr);
			}
		else if (idPtr->typePtr->form == FRM_ENUM) {
			if (sawSign)
				syntaxError(ABL_ERR_SYNTAX_INVALID_CONSTANT);
			newCaseItem->labelValue = idPtr->defn.info.constant.value.integer;
			return(idPtr->typePtr);
			}
		else
			return(&DummyType);
		}
	else if (curToken == TKN_STRING) {
		// STRING/CHAR TYPE...
		}
	else {
		syntaxError(ABL_ERR_SYNTAX_INVALID_CONSTANT);
		return(&DummyType);
	}
	return(&DummyType);
}

//---------------------------------------------------------------------------

void caseBranch (CaseItemPtr& caseItemHead, CaseItemPtr& caseItemTail, long& caseLabelCount, TypePtr expressionType) {

	//static CaseItemPtr oldCaseItemTail = NULL;
	CaseItemPtr oldCaseItemTail = caseItemTail;

	bool anotherLabel;
	do {
		TypePtr labelType = caseLabel(caseItemHead, caseItemTail, caseLabelCount);
		if (expressionType != labelType)
			syntaxError(ABL_ERR_SYNTAX_INCOMPATIBLE_TYPES);

		getToken();
		if (curToken == TKN_COMMA) {
			getToken();
			if (tokenIn(CaseLabelStartList))
				anotherLabel = true;
			else {
				syntaxError(ABL_ERR_SYNTAX_MISSING_CONSTANT);
				anotherLabel = false;
			}
			}
		else
			anotherLabel = false;
	} while (anotherLabel);

	//--------------
	// Error sync...
	synchronize(FollowCaseLabelList, statementStartList, NULL);
	ifTokenGetElseError(TKN_COLON, ABL_ERR_SYNTAX_MISSING_COLON);

	//-----------------------------------------------------------------
	// Fill in the branch location for each CaseItem for this branch...
	CaseItemPtr caseItem = (!oldCaseItemTail ? caseItemHead : oldCaseItemTail->next);
	//oldCaseItemTail = CaseItemTail;
	while (caseItem) {
		caseItem->branchLocation = codeBufferPtr;
		caseItem = caseItem->next;
	}

	if (curToken != TKN_END_CASE)
		do {
			statement();
			while (curToken == TKN_SEMICOLON)
				getToken();
			if (curToken == TKN_END_CASE)
				break;
		} while (tokenIn(statementStartList));

	ifTokenGetElseError(TKN_END_CASE, ABL_ERR_SYNTAX_MISSING_END_CASE);
	ifTokenGetElseError(TKN_SEMICOLON, ABL_ERR_SYNTAX_MISSING_SEMICOLON);
}

//---------------------------------------------------------------------------

void switchStatement (void) {

	//-------------------------
	// Init the branch table...
	getToken();
	char* branchTableLocation = crunchAddressMarker(NULL);

CaseItemPtr	caseItemHead = NULL;
CaseItemPtr	caseItemTail = NULL;
long caseLabelCount = 0;
	
//CaseItemHead = CaseItemTail = NULL;
//CaseLabelCount = 0;

	TypePtr expressionType = expression();

	//-----------------------------------------------------------------------------
	// NOTE: If we have subranges in ABL, we'll have to check in the following line
	// for a subrange, as well...
	if (((expressionType->form != FRM_SCALAR) && (expressionType->form != FRM_ENUM)) || (expressionType == RealTypePtr))
		syntaxError(ABL_ERR_SYNTAX_INCOMPATIBLE_TYPES);

	synchronize(FollowSwitchExpressionList, NULL, NULL);

	//----------------------------
	// Process each CASE branch...
	bool moreBranches = (curToken == TKN_CASE);
	char* caseEndChain = NULL;
	while (moreBranches) {
		getToken();
		if (tokenIn(CaseLabelStartList))
			caseBranch(caseItemHead, caseItemTail, caseLabelCount, expressionType);

		//---------------------------------------------------
		// Link another address marker at the end of the CASE
		// branch to point to the end of the CASE block...
		caseEndChain = crunchAddressMarker(caseEndChain);

		moreBranches = (curToken == TKN_CASE);
	}

	//if (curToken == TKN_DEFAULT) {
	//}

	//-------------------------
	// Emit the branch table...
	fixupAddressMarker(branchTableLocation);
	crunchInteger(caseLabelCount);
	CaseItemPtr caseItem = caseItemHead;
	while (caseItem) {
		crunchInteger(caseItem->labelValue);
		crunchOffset(caseItem->branchLocation);
		CaseItemPtr nextCaseItem = caseItem->next;
		AblStackHeap->Free(caseItem);
		caseItem = nextCaseItem;
	}

	ifTokenGetElseError(TKN_END_SWITCH, ABL_ERR_SYNTAX_MISSING_END_SWITCH);

	//--------------------------------------------
	// Patch up the case branch address markers...
	while (caseEndChain)
		caseEndChain = fixupAddressMarker(caseEndChain);

}

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

void statement (void) {

	//-------------------------------------------------------------------
	// NOTE: Since we currently don't support generic BEGIN/END (compound
	// statement) blocks...
	if ((curToken != TKN_CODE) /*&& (curToken != TKN_BEGIN)*/)
		crunchStatementMarker();
	
	switch (curToken) {
		case TKN_IDENTIFIER: {
			SymTableNodePtr IdPtr = NULL;
			
			//--------------------------------------------------------------
			// First, do we have an assignment statement or a function call?		
			searchAndFindAllSymTables(IdPtr);
		
			if ((IdPtr->defn.key == DFN_FUNCTION)/* || (IdPtr->defn.key == DFN_MODULE)*/) {
				RoutineKey key = IdPtr->defn.info.routine.key;
				if ((key == RTN_ASSERT) || (key == RTN_PRINT) || (key == RTN_CONCAT)) {
					bool uncrunch = ((key == RTN_ASSERT) && !AssertEnabled) ||
									((key == RTN_PRINT) && !PrintEnabled) ||
									((key == RTN_CONCAT) && !StringFunctionsEnabled);
					if (uncrunch) {
						uncrunchStatementMarker();
						Crunch = false;
					}
				}
				crunchSymTableNodePtr(IdPtr);
				getToken();
				SymTableNodePtr thisRoutineIdPtr = CurRoutineIdPtr;
				routineCall(IdPtr, 1);
				CurRoutineIdPtr = thisRoutineIdPtr;
				Crunch = true;
				}
			else
				assignmentStatement(IdPtr);
			}
			break;
		case TKN_REPEAT:
			repeatStatement();
			break;
		case TKN_WHILE:
			whileStatement();
			break;
		case TKN_IF:
			ifStatement();
			break;
		case TKN_FOR:
			forStatement();
			break;
		case TKN_SWITCH:
			switchStatement();
			break;
		case TKN_TRANS:
			if (!parsingFSM)
			{
				syntaxError (ABL_ERR_SYNTAX_TRANS_IN_NONFSM);
				break;
			}
			SymTableNodePtr IdPtr = NULL;
			Crunch = false;
			getToken ();
			Crunch = true;

			Verify (FSMLevel != 0);
			IdPtr = searchSymTable(wordString, SymTableDisplay[FSMLevel]);
//			searchLocalSymTable(IdPtr);
			if (IdPtr == NULL)
			{
				Verify (FSMLevel != 0);
				IdPtr = enterSymTable(wordString, &SymTableDisplay[FSMLevel]);
//				enterLocalSymTable(IdPtr);
				IdPtr->defn.key = DFN_STATE;
				IdPtr->defn.info.routine.key = RTN_FORWARD;
				IdPtr->defn.info.routine.paramCount = 0;
				IdPtr->defn.info.routine.totalParamSize = 0;
				IdPtr->defn.info.routine.totalLocalSize = 0;
				IdPtr->defn.info.routine.params = NULL;
				IdPtr->defn.info.routine.locals = NULL;
				IdPtr->defn.info.routine.localSymTable = NULL;
				IdPtr->defn.info.routine.codeSegment = NULL;
				IdPtr->library = CurLibrary;
				IdPtr->typePtr = &DummyType;
				IdPtr->labelIndex = 0;
				IdPtr->level = (unsigned char) FSMLevel;
			}
		
			if (IdPtr->defn.key == DFN_STATE)
			{
				crunchSymTableNodePtr(IdPtr);
				getToken ();
			}
			else
			{
				syntaxError (ABL_ERR_SYNTAX_TRANS_TO_NON_STATE);
			}
			break;
	}

	//---------------------------------------------------------------------
	// Now, make sure the statement is closed off with the proper block end
	// statement, if necessary (which is usually the case :).
	synchronize(statementEndList, NULL, NULL);
	if (tokenIn(statementStartList))
		syntaxError(ABL_ERR_SYNTAX_MISSING_SEMICOLON);
}
		
//***************************************************************************

}