Complete disaster-recovery snapshot: engine/game source, game data assets, VC6 toolchain + DX SDKs, build outputs, deployed game, and _UNUSED archive. Large binaries in Git LFS; text preserved byte-for-byte (core.autocrlf=false, no eol attributes). See RECOVERY.md for the one-clone rebuild procedure.
598 lines
16 KiB
C++
598 lines
16 KiB
C++
//***************************************************************************
|
|
//
|
|
// 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
|
|
//***************************************************************************
|
|
}
|
|
|