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.
578 lines
16 KiB
C++
578 lines
16 KiB
C++
//***************************************************************************
|
|
//
|
|
// 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);
|
|
}
|
|
|
|
//***************************************************************************
|
|
|
|
} |