//***************************************************************************
//
//								ABLEXEC.CPP
//
//***************************************************************************

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

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

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

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

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

#ifndef ABLDBUG_H
	#include "abldbug.h"
#endif

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

//***************************************************************************
namespace ABL
{

//--------
// GLOBALS

	ABLExecFunctionType routineExecFunction = NULL;

	char*         codeBuffer = NULL;
	char*         codeBufferPtr = NULL;
	char*         codeSegmentPtr = NULL;
	char*         codeSegmentLimit = NULL;
	char*         statementStartPtr = NULL;

	TokenCodeType     codeToken;
	long          execLineNumber;
	long          execStatementCount = 0;

	StackItem*        stack = NULL;
	StackItemPtr      tos = NULL;
	StackItemPtr      stackFrameBasePtr = NULL;
	StackItemPtr      StaticDataPtr = NULL;
	long*         StaticVariablesSizes = NULL;
long*         EternalVariablesSizes = NULL;
	long          eternalOffset = 0;
	long          MaxStaticVariables = 0;
long          MaxEternalVariables = 0;
	long          NumStaticVariables = 0;
	long          CurModuleHandle = 0;
	long          MaxCodeBufferSize = 0;
	bool          CallModuleInit = false;
	bool          InOrdersBlock = false;

	long          MaxLoopIterations = 100001;
	bool          AssertEnabled = false;
	bool          PrintEnabled = true;
	bool          StringFunctionsEnabled = true;
	bool          DebugCodeEnabled = false;
	bool          IncludeDebugInfo = true;
	bool          ProfileABL = false;
	bool          Crunch = true;

//----------
// EXTERNALS

	extern SymTableNodePtr  CurRoutineIdPtr;

	extern ModuleEntryPtr ModuleRegistry;
	extern ABLModulePtr*  ModuleInstanceRegistry;
	extern ABLModulePtr   CurModule;
	extern ABLModulePtr   CurLibrary;

	extern TokenCodeType  curToken;
	extern long       lineNumber;
	extern long       FileNumber;
	extern long       level;
	extern TypePtr      IntegerTypePtr;
	extern TypePtr      CharTypePtr;
	extern TypePtr      RealTypePtr;
	extern TypePtr      BooleanTypePtr;

	extern UserHeapPtr    AblCodeHeap;
	extern UserHeapPtr    AblStackHeap;

	extern StackItem    returnValue;

	extern bool       ExitWithReturn;
	extern bool       ExitFromTacOrder;

	extern DebuggerPtr    debugger;

//***************************************************************************
// CRUNCH/DECRUNCH routines
//***************************************************************************

	void crunchToken (void)
	{

		if (!Crunch)
			return;

		if (codeBufferPtr >= (codeBuffer + MaxCodeBufferSize - 100))
		{
			STOP(("ABL SyntaxError #%d", ABL_ERR_SYNTAX_CODE_SEGMENT_OVERFLOW));
		}
		else
		{
			*codeBufferPtr = (char) curToken;
			codeBufferPtr++;
		}
	}

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

	void uncrunchToken (void)
	{
		codeBufferPtr--;
	}

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

	void crunchSymTableNodePtr (SymTableNodePtr nodePtr)
	{

		if (!Crunch)
			return;

		if (codeBufferPtr >= (codeBuffer + MaxCodeBufferSize - 100))
		{
			STOP(("ABL SyntaxError #%d", ABL_ERR_SYNTAX_CODE_SEGMENT_OVERFLOW));
		}
		else
		{
			SymTableNodePtr* nodePtrPtr = (SymTableNodePtr*)codeBufferPtr;
			*nodePtrPtr = nodePtr;
			codeBufferPtr += sizeof(SymTableNodePtr);
		}
	}


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

	void uncrunchSymTableNodePtr (void)
	{

		//-------------------------------------
		// Pull sym table node ptr off the buffer
		codeBufferPtr -= sizeof(SymTableNodePtr);
	}

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

	void crunchStatementMarker (void)
	{

		if (!Crunch)
			return;

		if (codeBufferPtr >= (codeBuffer + MaxCodeBufferSize - 100))
		{
			STOP(("ABL SyntaxError #%d", ABL_ERR_SYNTAX_CODE_SEGMENT_OVERFLOW));
		}
		else
		{
			char saveCode = *(--codeBufferPtr);
			*codeBufferPtr = (char)TKN_STATEMENT_MARKER;
			codeBufferPtr++;
			if (IncludeDebugInfo)
			{
				*((unsigned char*)codeBufferPtr) = (unsigned char)FileNumber;
				codeBufferPtr += sizeof(unsigned char);
				*((long*)codeBufferPtr) = lineNumber;
				codeBufferPtr += sizeof(long);
			}
			*codeBufferPtr = saveCode;
			codeBufferPtr++;
		}
	}

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

	void uncrunchStatementMarker (void)
	{

		//-------------------------
		// Pull code off the buffer
		codeBufferPtr--;
		//-------------------------------
		// Pull debug info off the buffer
		if (IncludeDebugInfo)
			codeBufferPtr -= (sizeof(unsigned char) + sizeof(long));
		//-------------------------------------
		// Pull statement marker off the buffer
		codeBufferPtr--;
	}

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

	char* crunchAddressMarker (Address address)
	{

		if (!Crunch)
			return(NULL);

		char* saveCodeBufferPtr = NULL;

		if (codeBufferPtr >= (codeBuffer + MaxCodeBufferSize - 100))
		{
			STOP(("ABL SyntaxError #%d", ABL_ERR_SYNTAX_CODE_SEGMENT_OVERFLOW));
		}
		else
		{
			char saveCode = *(--codeBufferPtr);
			*codeBufferPtr = (char)TKN_ADDRESS_MARKER;
			codeBufferPtr++;
			saveCodeBufferPtr = codeBufferPtr;
			*((Address*)codeBufferPtr) = address;
			codeBufferPtr += sizeof(Address);
			*codeBufferPtr = saveCode;
			codeBufferPtr++;
		}

		return(saveCodeBufferPtr);
	}

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

	char* fixupAddressMarker (Address address)
	{

		if (!Crunch)
			return(NULL);

		char* oldAddress = *((Address*)address);

		*((long*)address) = codeBufferPtr - address;
		return(oldAddress);
	}

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

	void crunchInteger (long value)
	{

		if (!Crunch)
			return;

		if (codeBufferPtr >= (codeBuffer + MaxCodeBufferSize - 100))
		{
			STOP(("ABL SyntaxError #%d", ABL_ERR_SYNTAX_CODE_SEGMENT_OVERFLOW));
		}
		else
		{
			*((long*)codeBufferPtr) = value;
			codeBufferPtr += sizeof(long);
		}
	}

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

	void crunchOffset (Address address)
	{

		if (!Crunch)
			return;

		if (codeBufferPtr >= (codeBuffer + MaxCodeBufferSize - 100))
		{
			STOP(("ABL SyntaxError #%d", ABL_ERR_SYNTAX_CODE_SEGMENT_OVERFLOW));
		}
		else
		{
			*((long*)codeBufferPtr) = address - codeBufferPtr;
			codeBufferPtr += sizeof(long);
		}
	}

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

	char* createCodeSegment (long& codeSegmentSize)
	{

		codeSegmentSize = codeBufferPtr - codeBuffer + 1;
		char* codeSegment = (char*)AblCodeHeap->Malloc(codeSegmentSize);
		if (!codeSegment)
			ABL_Fatal(0, " ABL: Unable to AblCodeHeap->malloc code segment ");

		for (long i = 0; i < codeSegmentSize; i++)
			codeSegment[i] = codeBuffer[i];

		codeBufferPtr = codeBuffer;
		return(codeSegment);
	}


//***************************************************************************
	void pushStackFrameHeader (long oldLevel, long newLevel)
	{

		//-----------------------------------
		// Make space for the return value...
		pushInteger(0);

		StackFrameHeaderPtr headerPtr = (StackFrameHeaderPtr)stackFrameBasePtr;

		//----------------------------------------------------------------------
		// STATIC LINK
		// Currently, let's not allow functions defined within functions. Assume
		// the old scope level equals the new scope level, for now.
		if (newLevel == -1)
		{
			//--------------------------------------------------------------------
			// Calling a library function, so push a NULL static link since
			// it's scope is in a different module than the calling function.
			// Note that global variables in libraries should be STATIC, otherwise
			// they're never in scope! Weird "feature" which we may want
			// to fix later...
			pushAddress(NULL);
		}
		else if (newLevel == oldLevel + 1)
		{
			//----------------------------------------------------------------
			// Calling a routine nested within the caller, so push the pointer
			// to the caller's stack frame. In ABL, as currently defined
			// (2/22/96), this should only be when a module's code section is
			// calling a function...
			pushAddress((Address)headerPtr);
		}
		else if (newLevel == oldLevel)
		{
			//---------------------------------------------------------------
			// Calling a function at the same scope level. We like that! Push
			// pointer to stack frame of their common parent...
			pushAddress(headerPtr->staticLink.address);
		}
		else
		{
			//-------------------------------------------------------
			// Oops. We don't want nested functions, for now, in ABL.
			STOP(("ABL SyntaxError #%d", ABL_ERR_RUNTIME_NESTED_FUNCTION_CALL));
		}
		pushAddress((Address)stackFrameBasePtr);

		//---------------------------
		// Push the return address...
		pushAddress(0);
	}

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

	void allocLocal (TypePtr typePtr)
	{

		if (typePtr == IntegerTypePtr)
			pushInteger(0);
		else if (typePtr == RealTypePtr)
			pushReal((float)0.0);
		else if (typePtr == BooleanTypePtr)
			pushByte(0);
		else if (typePtr == CharTypePtr)
			pushByte(0);
		else
			switch (typePtr->form)
			{
				case FRM_ENUM:
					pushInteger(0);
					break;
// NOTE: We currently are not supporting sub ranges, until
// we really want 'em...
//			case FRM_SUBRANGE:
//				allocLocal(typePtr->info.subrange.rangeTypePtr);
//				break;
				case FRM_ARRAY:
					char* ptr = (char*)AblStackHeap->Malloc(typePtr->size);
					if (!ptr)
						ABL_Fatal(0, " ABL: Unable to AblStackHeap->malloc local array ");
					pushAddress((Address)ptr);
					break;
			}
	}

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

	void freeLocal (SymTableNodePtr idPtr)
	{

		//---------------------------------------
		// Frees data allocated on local stack...

		TypePtr typePtr = (TypePtr)(idPtr->typePtr);

		StackItemPtr itemPtr = NULL;
		if (((typePtr->form == FRM_ARRAY)	/* || (typePtr->form == FRM_RECORD)*/) &&
				(idPtr->defn.key != DFN_REFPARAM))
		{
			switch (idPtr->defn.info.data.varType)
			{
				case VAR_TYPE_NORMAL:
					itemPtr = stackFrameBasePtr + idPtr->defn.info.data.offset;
					break;
//			case VAR_TYPE_ETERNAL:
//				itemPtr = stack + idPtr->defn.info.data.offset;
//				break;
//			case VAR_TYPE_STATIC:
//				itemPtr = StaticDataPtr + idPtr->defn.info.data.offset;
//				break;
			}
			if (!itemPtr)
				runtimeError(0);
			else
				AblStackHeap->Free(itemPtr->address);
		}
	}

//***************************************************************************
// FUNCTION ENTRY/EXIT routines
//***************************************************************************

	void routineEntry (SymTableNodePtr routineIdPtr)
	{

		if (debugger)
			debugger->traceRoutineEntry(routineIdPtr);

		memset(&returnValue, 0, sizeof(StackItem));

		//------------------------------
		// Switch to new code segment...
		codeSegmentPtr = routineIdPtr->defn.info.routine.codeSegment;

		//----------------------------------------------
		// Allocate local variables onto system stack...
		for (SymTableNodePtr varIdPtr = (SymTableNodePtr)(routineIdPtr->defn.info.routine.locals);
				varIdPtr != NULL;
				varIdPtr = varIdPtr->next)
			if (varIdPtr->defn.info.data.varType == VAR_TYPE_NORMAL)
				allocLocal((TypePtr)(varIdPtr->typePtr));
	}

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

	void routineExit (SymTableNodePtr routineIdPtr)
	{

		if (debugger)
			debugger->traceRoutineExit(routineIdPtr);

		//-----------------------------------------
		// De-alloc parameters & local variables...

		for (SymTableNodePtr idPtr = (SymTableNodePtr)(routineIdPtr->defn.info.routine.params);
				idPtr != NULL;
				idPtr = idPtr->next)
			freeLocal(idPtr);

		for (idPtr = (SymTableNodePtr)(routineIdPtr->defn.info.routine.locals);
				idPtr != NULL;
				idPtr = idPtr->next)
			if (idPtr->defn.info.data.varType == VAR_TYPE_NORMAL)
				freeLocal(idPtr);

		StackFrameHeaderPtr headerPtr = (StackFrameHeaderPtr)stackFrameBasePtr;
		codeSegmentPtr = headerPtr->returnAddress.address;

		if (routineIdPtr->typePtr == NULL)
			tos = stackFrameBasePtr - 1;
		else
			tos	= stackFrameBasePtr;

		stackFrameBasePtr = (StackItemPtr)headerPtr->dynamicLink.address;
	}

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

	void execute (SymTableNodePtr routineIdPtr)
	{

		SymTableNodePtr thisRoutineIdPtr = CurRoutineIdPtr;

		CurRoutineIdPtr = routineIdPtr;


		routineEntry(routineIdPtr);

		//----------------------------------------------------
		// Now, search this module for the function we want...
		if (CallModuleInit)
		{
			CallModuleInit = false;
			SymTableNodePtr initFunctionIdPtr = searchSymTable("init", ModuleRegistry[CurModule->getHandle()].moduleIdPtr->defn.info.routine.localSymTable);
			if (initFunctionIdPtr)
			{
				execRoutineCall(initFunctionIdPtr);
				//-------------------------------------------------------------------------
				// Since we're calling the function directly, we need to compensate for the
				// codeSegmentPtr being incremented by 1 in the normal execRoutineCall...
				codeSegmentPtr--;
			}
		}

		if (routineIdPtr->defn.key == DFN_FSM)
		{
			execRoutineCall (routineIdPtr->defn.info.routine.state);
			codeSegmentPtr--;
			routineExit(routineIdPtr);
		}
		else
		{
			getCodeToken();

			execStatement();

			//---------------------------------------------
			// In case we exited with a return statement...
			ExitWithReturn = false;
			ExitFromTacOrder = false;

			routineExit(routineIdPtr);
		}


		CurRoutineIdPtr = thisRoutineIdPtr;
	}

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

	void executeChild (SymTableNodePtr routineIdPtr, SymTableNodePtr childRoutineIdPtr)
	{

		// THIS DOES NOT SUPPORT CALLING FUNCTIONS WITH PARAMETERS YET!

		SymTableNodePtr thisRoutineIdPtr = CurRoutineIdPtr;

		CurRoutineIdPtr = routineIdPtr;

		routineEntry(routineIdPtr);

		//----------------------------------------------------
		// Now, search this module for the function we want...
		SymTableNodePtr initFunctionIdPtr = NULL;
		if (CallModuleInit)
		{
			CallModuleInit = false;
			initFunctionIdPtr = searchSymTable("init", routineIdPtr->defn.info.routine.localSymTable);
			if (initFunctionIdPtr)
			{
				execRoutineCall(initFunctionIdPtr);
				//-------------------------------------------------------------------------
				// Since we're calling the function directly, we need to compensate for the
				// codeSegmentPtr being incremented by 1 in the normal execRoutineCall...
				codeSegmentPtr--;
			}
		}

		if (initFunctionIdPtr != childRoutineIdPtr)
		{
			//-----------------------------------------------------------------------
			// If we're calling the module's init function, and we just did above,
			// don't call it again! That's why we make the check on the above line...
			execRoutineCall(childRoutineIdPtr);
			//-------------------------------------------------------------------------
			// Since we're calling the function directly, we need to compensate for the
			// codeSegmentPtr being incremented by 1 in the normal execRoutineCall...
			codeSegmentPtr--;
		}

		//---------------------------------------------
		// In case we exited with a return statement...
		ExitWithReturn = false;
		ExitFromTacOrder = false;

		routineExit(routineIdPtr);

		CurRoutineIdPtr = thisRoutineIdPtr;
	}

//***************************************************************************
// MISC routines
//***************************************************************************
}

