//***************************************************************************
//
//								ABLEXPR.CPP
//
//***************************************************************************

#include "MW4Headers.hpp" 
#include <string.h>
#include <stdio.h>
#include <stdlib.h>

#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
namespace ABL
{

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

extern TokenCodeType	curToken;
extern char				tokenString[];
extern char				wordString[];
extern Literal			curLiteral;

extern SymTableNodePtr	SymTableDisplay[];
extern long				level;

extern TypePtr			IntegerTypePtr, CharTypePtr, RealTypePtr, BooleanTypePtr;
extern Type				DummyType;

extern TokenCodeType	statementEndList[];

extern UserHeapPtr		AblSymTableHeap;

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

TokenCodeType relationalOperatorList[] = {
	TKN_LT,
	TKN_LE,
	TKN_EQUALEQUAL,
	TKN_NE,
	TKN_GE,
	TKN_GT,
	TKN_NONE
};

TokenCodeType addOperatorList[] = {
	TKN_PLUS,
	TKN_MINUS,
	TKN_OR,
	TKN_NONE
};

TokenCodeType multiplyOperatorList[] = {
	TKN_STAR,
	TKN_FSLASH,
	TKN_DIV,		// we'll probably want to make this covered with FSLASH
	TKN_MOD,
	TKN_AND,
	TKN_NONE
};

//***************************************************************************
// MISC
//***************************************************************************

/*
inline void ifTokenGet (TokenCodeType tokenCode) {

	if (curToken == tokenCode)
		getToken();
}

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

inline void ifTokenGetElseError (TokenCodeType tokenCode, SyntaxErrorType errCode) {

	if (curToken == tokenCode)
		getToken();
	else
		syntaxError(errCode);
}

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

*/

/*inline*/ TypePtr baseType (TypePtr thisType) {

	//---------------------------------------------------------------------
	// Since we don't have a sub-range type, block this out for now.
	// However, if we decide to put a subrange type into ABL, the following
	// three lines should be included...

	//if (thisType->form == FRM_SUBRANGE)
	//	return(thisType->info.subrange.rangeTypePtr;
	//else
		return(thisType);
}

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

inline bool integerOperands (TypePtr type1, TypePtr type2) {

	return((type1 == IntegerTypePtr) && (type2 == IntegerTypePtr));
}

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

inline bool realOperands (TypePtr type1, TypePtr type2) {

	if (type1 == RealTypePtr)
		return((type2 == RealTypePtr) || (type2 == IntegerTypePtr));
	else if (type2 == RealTypePtr)
		return(type1 == IntegerTypePtr);
	else
		return(false);
}

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

inline bool booleanOperands (TypePtr type1, TypePtr type2) {

	return((type1 == BooleanTypePtr) && (type2 == BooleanTypePtr));
}

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

void checkRelationalOpTypes (TypePtr type1, TypePtr type2) {

	if ((type1 == NULL) || (type2 == NULL))
	{
		syntaxError(ABL_ERR_SYNTAX_INCOMPATIBLE_TYPES);
		return;
	}
	if ((type1 == type2) && ((type1->form == FRM_SCALAR) || (type1->form == FRM_ENUM)))
		return;
	if (((type1 == IntegerTypePtr) && (type2 == RealTypePtr)) ||
		((type2 == IntegerTypePtr) && (type1 == RealTypePtr)))
		return;
	if ((type1->form == FRM_ARRAY) && (type2->form == FRM_ARRAY) &&
		(type1->info.array.elementTypePtr == CharTypePtr) && (type2->info.array.elementTypePtr == CharTypePtr) &&
		(type1->info.array.elementCount == type2->info.array.elementCount))
		return;
	syntaxError(ABL_ERR_SYNTAX_INCOMPATIBLE_TYPES);
}

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

long isAssignTypeCompatible (TypePtr type1, TypePtr type2) {

	type1 = baseType(type1);
	type2 = baseType(type2);
	
	if (type1 == type2)
		return(1);
		
	if ((type1 == RealTypePtr) && (type2 == IntegerTypePtr))
		return(1);

	if ((type1->form == FRM_ARRAY) && (type2->form == FRM_ARRAY) &&
		(type1->info.array.elementTypePtr == CharTypePtr) && (type2->info.array.elementTypePtr == CharTypePtr) &&
		(type1->info.array.elementCount >= type2->info.array.elementCount))
		return(1);

	return(0);
}

//***************************************************************************
// EXPRESSION routines
//***************************************************************************

TypePtr variable (SymTableNodePtr variableIdPtr) {

	TypePtr typePtr = (TypePtr)(variableIdPtr->typePtr);
	DefinitionType defnKey = variableIdPtr->defn.key;

	crunchSymTableNodePtr(variableIdPtr);

	switch (defnKey) {
		case DFN_VAR:
		case DFN_VALPARAM:
		case DFN_REFPARAM:
		case DFN_FUNCTION:
		//case DFN_MODULE:
		case DFN_UNDEFINED:
			break;
		default:
			typePtr = &DummyType;
			syntaxError(ABL_ERR_SYNTAX_INVALID_IDENTIFIER_USAGE);
	}

	getToken();

	//---------------------------------------------------------------------
	// There should not be a parameter list. However, if there is, parse it
	// for error recovery...
	if (curToken == TKN_LPAREN) {
		syntaxError(ABL_ERR_SYNTAX_UNEXPECTED_TOKEN);
		actualParamList(variableIdPtr, 0);
		return(typePtr);
	}

	//--------------------------------------------------------------------
	// Subscripts or field designators (when/if we support record types :)
	while ((curToken == TKN_LBRACKET) || (curToken == TKN_PERIOD)) {
		if (curToken == TKN_LBRACKET)
			typePtr = arraySubscriptList(typePtr);
		else
			STOP(("666???"));
			//typePtr = recordField(typePtr);
	}

	return(typePtr);
}

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

TypePtr arraySubscriptList (TypePtr typePtr) {

	TypePtr indexTypePtr = NULL;
	TypePtr elementTypePtr = NULL;
	TypePtr subscriptTypePtr = NULL;

	do {
		if (typePtr->form == FRM_ARRAY) {
			indexTypePtr = typePtr->info.array.indexTypePtr;
			elementTypePtr = typePtr->info.array.elementTypePtr;

			getToken();
			subscriptTypePtr = expression();

			//-------------------------------------------------------------
			// If the subscript expression isn't assignment type compatible
			// with its corresponding subscript type, we're screwed...
			if (!isAssignTypeCompatible(indexTypePtr, subscriptTypePtr))
				syntaxError(ABL_ERR_SYNTAX_INCOMPATIBLE_TYPES);

			typePtr = elementTypePtr;
			}
		else {
			syntaxError(ABL_ERR_SYNTAX_TOO_MANY_SUBSCRIPTS);
			while ((curToken != TKN_RBRACKET) && !tokenIn(statementEndList))
				getToken();
		}
	} while (curToken == TKN_COMMA);

	ifTokenGetElseError(TKN_RBRACKET, ABL_ERR_SYNTAX_MISSING_RBRACKET);
	return(typePtr);
}

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

TypePtr factor (void) {

	TypePtr thisType = NULL;
	switch (curToken) {
		case TKN_IDENTIFIER: {
			SymTableNodePtr IdPtr = NULL;
			searchAndFindAllSymTables(IdPtr);
			switch (IdPtr->defn.key) {
				case DFN_FUNCTION:
					crunchSymTableNodePtr(IdPtr);
					getToken();
					thisType = routineCall(IdPtr, 1);
					break;
				case DFN_CONST:
					crunchSymTableNodePtr(IdPtr);
					getToken();
					thisType = (TypePtr)(IdPtr->typePtr);
					break;
				default:
					thisType = (TypePtr)variable(IdPtr);
					break;
			}
			}
			break;
		case TKN_NUMBER: {
			SymTableNodePtr thisNode = searchSymTable(tokenString, SymTableDisplay[1]);
			if (!thisNode)
//--------------------------------------------------------------------------
// NOTE: The following commented-out line is from MAK. However, I believe we
// want the ampersand, as inserted in the following line...
//				thisNode = enterSymTable(tokenString, SymTableDisplay[1]);
				thisNode = enterSymTable(tokenString, &SymTableDisplay[1]);
			if (curLiteral.type == LIT_INTEGER) {
				thisNode->typePtr = IntegerTypePtr;
				thisType = (TypePtr)(thisNode->typePtr);
				thisNode->defn.info.constant.value.integer = curLiteral.value.integer;
				}
			else {
				thisNode->typePtr = RealTypePtr;
				thisType = (TypePtr)(thisNode->typePtr);
				thisNode->defn.info.constant.value.real = curLiteral.value.real;
			}
			crunchSymTableNodePtr(thisNode);
			getToken();
			}
			break;
		case TKN_STRING: {
			long length = strlen(curLiteral.value.string);
			SymTableNodePtr thisNode = searchSymTable(tokenString, SymTableDisplay[1]);
			if (!thisNode)
//--------------------------------------------------------------------------
// NOTE: The following commented-out line is from MAK. However, I believe we
// want the ampersand, as inserted in the following line...
//				thisNode = enterSymTable(tokenString, SymTableDisplay[1]);
				thisNode = enterSymTable(tokenString, &SymTableDisplay[1]);
			if (length == 1) {
				thisNode->defn.info.constant.value.character = curLiteral.value.string[0];
				thisType = CharTypePtr;
				}
			else {
				thisNode->typePtr = thisType = makeStringType(length);
				thisNode->info = (char*)AblSymTableHeap->Malloc(length + 1);
				if (!thisNode->info)
					ABL_Fatal(0, " ABL: Unable to AblSymTableHeap->malloc string literal ");
				strcpy(thisNode->info, curLiteral.value.string);
			}

			crunchSymTableNodePtr(thisNode);

			getToken();
			}
			break;
		case TKN_NOT:
			getToken();
			thisType = factor();
			break;
		case TKN_LPAREN:
			getToken();
			thisType = expression();
			ifTokenGetElseError(TKN_RPAREN, ABL_ERR_SYNTAX_MISSING_RPAREN);
			break;
		default:
			syntaxError(ABL_ERR_SYNTAX_INVALID_EXPRESSION);
			thisType = &DummyType;
			break;
	}
	return(thisType);
}

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

TypePtr term (void) {

	//-------------------------
	// Grab the first factor...
	TypePtr resultType = factor();
	
	//------------------------------------------------------------------
	// Now, continue grabbing factors separated by multiply operators...
	while (tokenIn(multiplyOperatorList)) {
		TokenCodeType op = curToken;
		resultType = baseType(resultType);
		
		getToken();
		TypePtr secondType = baseType(factor());
		
		switch (op) {
			case TKN_STAR:
				if (integerOperands(resultType, secondType)) {
					//---------------------------------------------------
					// Both operands are integer, so result is integer...
					resultType = IntegerTypePtr;
					}
				else if (realOperands(resultType, secondType)) {
					//----------------------------------------------------
					// Both real operands, or mixed (real and integer)...
					resultType = RealTypePtr;
					}
				else {
					syntaxError(ABL_ERR_SYNTAX_INCOMPATIBLE_TYPES);
					resultType = &DummyType;
				}
				break;
			case TKN_FSLASH:
				if (integerOperands(resultType, secondType)) {
					//---------------------------------------------------
					// Both operands are integer, so result is integer...
					resultType = IntegerTypePtr;
					}
				else if (realOperands(resultType, secondType)) {
					//----------------------------------------------------
					// Both real operands, or mixed (real and integer)...
					resultType = RealTypePtr;
					}
				else {
					syntaxError(ABL_ERR_SYNTAX_INCOMPATIBLE_TYPES);
					resultType = &DummyType;
				}
				break;
			case TKN_DIV:
			case TKN_MOD:
				//----------------------------------------------------------
				// Both operands should be integer, and result is integer...
				if (!integerOperands(resultType, secondType))
					syntaxError(ABL_ERR_SYNTAX_INCOMPATIBLE_TYPES);
				resultType = IntegerTypePtr;
				break;
			case TKN_AND:
				if (!booleanOperands(resultType, secondType))
					syntaxError(ABL_ERR_SYNTAX_INCOMPATIBLE_TYPES);
				resultType = BooleanTypePtr;								
				break;
		}
	}
	return(resultType);
}

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

TypePtr simpleExpression (void) {

	bool usedUnaryOp = false;
	TokenCodeType unaryOp = TKN_PLUS;
	
	if ((curToken == TKN_PLUS) || (curToken == TKN_MINUS)) {
		unaryOp = curToken;
		usedUnaryOp = true;
		getToken();
	}
	
	//------------------------------------------------
	// Grab the first term in the simple expression...
	TypePtr resultType = term();
	
	if (usedUnaryOp && (baseType(resultType) != IntegerTypePtr) && (resultType != RealTypePtr))
		syntaxError(ABL_ERR_SYNTAX_INCOMPATIBLE_TYPES);
		
	//---------------------------------------------------
	// Continue to process all terms in the expression...
	while (tokenIn(addOperatorList)) {
		TokenCodeType op = curToken;
		resultType = baseType(resultType);
		
		getToken();
		TypePtr secondType = baseType(term());
		
		switch (op) {
			case TKN_PLUS:
			case TKN_MINUS:
				if (integerOperands(resultType, secondType)) {
					//---------------------------------------------------
					// Both operands are integer, so result is integer...
					resultType = IntegerTypePtr;
					}
				else if (realOperands(resultType, secondType)) {
					//----------------------------------------------------
					// Both real operands, or mixed (real and integer)...
					resultType = RealTypePtr;
					}
				else {
					syntaxError(ABL_ERR_SYNTAX_INCOMPATIBLE_TYPES);
					resultType = &DummyType;
				}
				break;
			case TKN_OR:
				if (!booleanOperands(resultType, secondType))
					syntaxError(ABL_ERR_SYNTAX_INCOMPATIBLE_TYPES);
				resultType = BooleanTypePtr;
				break;
		}
	}
	return(resultType);
}				

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

TypePtr expression (void) {

	//------------------------------------
	// Grab the first simple expression...
	TypePtr resultType = simpleExpression();

	if (tokenIn(relationalOperatorList)) {
		resultType = baseType(resultType);

		//---------------------------------------
		// Snatch the second simple expression...
		getToken();
		TypePtr secondType = baseType(simpleExpression());

		checkRelationalOpTypes(resultType, secondType);
		resultType = BooleanTypePtr;
	}
	return(resultType);
}

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

}