
//**************************************************************************
//**
//** parse.c
//**
//**************************************************************************

// HEADER FILES ------------------------------------------------------------

#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <malloc.h>
#include <stdio.h>

#include "common.h"
#include "parse.h"
#include "symbol.h"
#include "pcode.h"
#include "token.h"
#include "error.h"
#include "misc.h"
#include "strlist.h"

// MACROS ------------------------------------------------------------------

#define MAX_STATEMENT_DEPTH 128
#define MAX_BREAK 128
#define MAX_CONTINUE 128
#define MAX_CASE 128
#define EXPR_STACK_DEPTH 64

// TYPES -------------------------------------------------------------------

typedef enum
{
	STMT_SCRIPT,
	STMT_IF,
	STMT_ELSE,
	STMT_DO,
	STMT_WHILEUNTIL,
	STMT_SWITCH,
	STMT_FOR
} statement_t;

typedef struct
{
	int level;
	int addressPtr;
} breakInfo_t;

typedef struct
{
	int level;
	int addressPtr;
} continueInfo_t;

typedef struct
{
	int level;
	int value;
	boolean isDefault;
	int address;
} caseInfo_t;

typedef struct prefunc_s
{
	struct prefunc_s *next;
	symbolNode_t *sym;
	int address;
	int argcount;
	int line;
	char *source;
} prefunc_t;

// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------

// PUBLIC FUNCTION PROTOTYPES ----------------------------------------------

// PRIVATE FUNCTION PROTOTYPES ---------------------------------------------

static void CountScript(int type);
static void Outside(void);
static void OuterScript(void);
static void OuterFunction(void);
static void OuterMapVar(void);
static void OuterWorldVar(boolean isGlobal);
static void OuterSpecialDef(void);
static void OuterDefine(boolean force);
static void OuterInclude(void);
static void OuterImport(void);
static void OuterLocalizedStrings(void);
static boolean ProcessStatement(statement_t owner);
static void LeadingCompoundStatement(statement_t owner);
static void LeadingVarDeclare(void);
static void LeadingLineSpecial(boolean executewait);
static void LeadingIdentifier(void);
static void BuildPrintString(void);
static void LeadingPrint(void);
static void LeadingHudMessage(void);
static void LeadingVarAssign(symbolNode_t *sym);
static pcd_t GetAssignPCD(tokenType_t token, symbolType_t symbol);
static void LeadingInternFunc(symbolNode_t *sym);
static void LeadingScriptFunc(symbolNode_t *sym);
static void LeadingSuspend(void);
static void LeadingTerminate(void);
static void LeadingRestart(void);
static void LeadingReturn(void);
static void LeadingIf(void);
static void LeadingFor(void);
static void LeadingWhileUntil(void);
static void LeadingDo(void);
static void LeadingSwitch(void);
static void LeadingCase(void);
static void LeadingDefault(void);
static void LeadingBreak(void);
static void LeadingContinue(void);
static void LeadingCreateTranslation(void);
static void LeadingIncDec(int token);
static void PushCase(int value, boolean isDefault);
static caseInfo_t *GetCaseInfo(void);
static int CaseInfoCmp(const void *a, const void *b);
static boolean DefaultInCurrent(void);
static void PushBreak(void);
static void WriteBreaks(void);
static boolean BreakAncestor(void);
static void PushContinue(void);
static void WriteContinues(int address);
static boolean ContinueAncestor(void);
static void ProcessInternFunc(symbolNode_t *sym);
static void ProcessScriptFunc(symbolNode_t *sym, boolean discardReturn);
static void EvalExpression(void);
static void ExprLevA(void);
static void ExprLevB(void);
static void ExprLevC(void);
static void ExprLevD(void);
static void ExprLevE(void);
static void ExprLevF(void);
static void ExprLevG(void);
static void ExprLevH(void);
static void ExprLevI(void);
static void ExprLevJ(void);
static void ExprFactor(void);
static void ConstExprFactor(void);
static void SendExprCommand(pcd_t pcd);
static void PushExStk(int value);
static int PopExStk(void);
static pcd_t TokenToPCD(tokenType_t token);
static pcd_t GetPushVarPCD(symbolType_t symType);
static pcd_t GetIncDecPCD(tokenType_t token, symbolType_t symbol);
static int EvalConstExpression(void);
static void ParseArrayIndices(symbolNode_t *sym);
static void InitializeArray(symbolNode_t *sym, int dims[MAX_ARRAY_DIMS], int size);
static symbolNode_t *DemandSymbol(char *name);
static symbolNode_t *SpeculateSymbol(char *name, boolean hasReturn);
static symbolNode_t *SpeculateFunction(const char *name, boolean hasReturn);
static void UnspeculateFunction(symbolNode_t *sym);
static void AddScriptFuncRef(symbolNode_t *sym, int address, int argcount);
static void CheckForUndefinedFunctions(void);
static void SkipBraceBlock(int depth);

// EXTERNAL DATA DECLARATIONS ----------------------------------------------

// PUBLIC DATA DEFINITIONS -------------------------------------------------

int pa_ScriptCount;
struct ScriptTypes *pa_TypedScriptCounts;
int pa_MapVarCount;
int pa_WorldVarCount;
int pa_GlobalVarCount;
int pa_WorldArrayCount;
int pa_GlobalArrayCount;
enum ImportModes ImportMode = IMPORT_None;
boolean ExporterFlagged;
boolean pa_ConstExprIsString;

// PRIVATE DATA DEFINITIONS ------------------------------------------------

static int ScriptVarCount;
static statement_t StatementHistory[MAX_STATEMENT_DEPTH];
static int StatementIndex;
static breakInfo_t BreakInfo[MAX_BREAK];
static int BreakIndex;
static continueInfo_t ContinueInfo[MAX_CONTINUE];
static int ContinueIndex;
static caseInfo_t CaseInfo[MAX_CASE];
static int CaseIndex;
static int StatementLevel;
static int ExprStack[EXPR_STACK_DEPTH];
static int ExprStackIndex;
static boolean ConstantExpression;
static symbolNode_t *InsideFunction;
static prefunc_t *FillinFunctions;
static prefunc_t **FillinFunctionsLatest = &FillinFunctions;
static boolean ArrayHasStrings;

static int AdjustStmtLevel[] =
{
	0,		// STMT_SCRIPT
	0,		// STMT_IF
	0,		// STMT_ELSE
	1,		// STMT_DO
	1,		// STMT_WHILEUNTIL
	1,		// STMT_SWITCH
	1		// STMT_FOR
};

static boolean IsBreakRoot[] =
{
	NO,		// STMT_SCRIPT
	NO,		// STMT_IF
	NO,		// STMT_ELSE
	YES,	// STMT_DO
	YES,	// STMT_WHILEUNTIL
	YES,	// STMT_SWITCH
	YES		// STMT_FOR
};

static boolean IsContinueRoot[] =
{
	NO,		// STMT_SCRIPT
	NO,		// STMT_IF
	NO,		// STMT_ELSE
	YES,	// STMT_DO
	YES,	// STMT_WHILEUNTIL
	NO,		// STMT_SWITCH
	YES		// STMT_FOR
};

static tokenType_t LevFOps[] =
{
	TK_EQ,
	TK_NE,
	TK_NONE
};

static tokenType_t LevGOps[] =
{
	TK_LT,
	TK_LE,
	TK_GT,
	TK_GE,
	TK_NONE
};

static tokenType_t LevHOps[] =
{
	TK_LSHIFT,
	TK_RSHIFT,
	TK_NONE
};

static tokenType_t LevIOps[] =
{
	TK_PLUS,
	TK_MINUS,
	TK_NONE
};

static tokenType_t LevJOps[] =
{
	TK_ASTERISK,
	TK_SLASH,
	TK_PERCENT,
	TK_NONE
};

static tokenType_t AssignOps[] =
{
	TK_ASSIGN,
	TK_ADDASSIGN,
	TK_SUBASSIGN,
	TK_MULASSIGN,
	TK_DIVASSIGN,
	TK_MODASSIGN,
	TK_NONE
};

static struct ScriptTypes ScriptCounts[] =
{
	{ "closed",			0,							0 },
	{ "open",			OPEN_SCRIPTS_BASE,			0 },
	{ "respawn",		RESPAWN_SCRIPTS_BASE,		0 },
	{ "death",			DEATH_SCRIPTS_BASE,			0 },
	{ "enter",			ENTER_SCRIPTS_BASE,			0 },
	{ "pickup",			PICKUP_SCRIPTS_BASE,		0 },
	{ "bluereturn",		BLUE_RETURN_SCRIPTS_BASE,	0 },
	{ "redreturn",		RED_RETURN_SCRIPTS_BASE,	0 },
	{ "whitereturn",	WHITE_RETURN_SCRIPTS_BASE,	0 },
	{ "lightning",		LIGHTNING_SCRIPTS_BASE,		0 },
	{ "disconnect",		DISCONNECT_SCRIPTS_BASE,	0 },
	{ "unloading",		UNLOADING_SCRIPTS_BASE,		0 },
	{ NULL,				-1,							0 }
};

// CODE --------------------------------------------------------------------

//==========================================================================
//
// PA_Parse
//
//==========================================================================

void PA_Parse(void)
{
	int i;

	pa_ScriptCount = 0;
	pa_TypedScriptCounts = ScriptCounts;
	for (i = 0; ScriptCounts[i].TypeName != NULL; i++)
	{
		ScriptCounts[i].TypeCount = 0;
	}
	pa_MapVarCount = 0;
	pa_WorldVarCount = 0;
	pa_GlobalVarCount = 0;
	pa_WorldArrayCount = 0;
	pa_GlobalArrayCount = 0;
	TK_NextToken();
	Outside();
	CheckForUndefinedFunctions();
	ERR_Finish();
}

//==========================================================================
//
// CountScript
//
//==========================================================================

static void CountScript(int type)
{
	int i;

	for (i = 0; ScriptCounts[i].TypeName != NULL; i++)
	{
		if (ScriptCounts[i].TypeBase == type)
		{
			if (type != 0)
			{
				MS_Message(MSG_DEBUG, "Script type: %s\n",
					ScriptCounts[i].TypeName);
			}
			ScriptCounts[i].TypeCount++;
			return;
		}
	}
	return;
}

//==========================================================================
//
// Outside
//
//==========================================================================

static void Outside(void)
{
	boolean done;

	done = NO;
	while(done == NO)
	{
		switch(tk_Token)
		{
		case TK_EOF:

			done = YES;
			break;
		case TK_SCRIPT:
			OuterScript();
			break;
		case TK_FUNCTION:
			OuterFunction();
			break;
		case TK_INT:
		case TK_STR:
		case TK_BOOL:
			OuterMapVar();
			break;
		case TK_WORLD:
			OuterWorldVar(NO);
			break;
		case TK_GLOBAL:
			OuterWorldVar(YES);
			break;
		case TK_SPECIAL:
			OuterSpecialDef();
			break;
		case TK_LOCALIZEDSTRINGS:
			OuterLocalizedStrings();
			break;
		case TK_NUMBERSIGN:
			TK_NextToken();
			switch(tk_Token)
			{
			case TK_DEFINE:
				OuterDefine(NO);
				break;
			case TK_LIBDEFINE:
				OuterDefine(YES);
				break;
			case TK_INCLUDE:
				OuterInclude();
				break;
			case TK_NOCOMPACT:
				if(ImportMode != IMPORT_Importing)
				{
					if(pc_Address != 8)
					{
						ERR_Error(ERR_NOCOMPACT_NOT_HERE, YES);
					}
					MS_Message(MSG_DEBUG, "Forcing NoShrink\n");
					pc_NoShrink = TRUE;
				}
				TK_NextToken();
				break;
			case TK_WADAUTHOR:
				if(ImportMode != IMPORT_Importing)
				{
					MS_Message(MSG_DEBUG, "Will write WadAuthor-compatible object\n");
					MS_Message(MSG_NORMAL, "You don't need to use #wadauthor anymore.\n");
					pc_WadAuthor = TRUE;
				}
				TK_NextToken();
				break;
			case TK_NOWADAUTHOR:
				if(ImportMode != IMPORT_Importing)
				{
					MS_Message(MSG_DEBUG, "Will write WadAuthor-incompatible object\n");
					pc_WadAuthor = FALSE;
				}
				TK_NextToken();
				break;
			case TK_ENCRYPTSTRINGS:
				if(ImportMode != IMPORT_Importing)
				{
					MS_Message(MSG_DEBUG, "Strings will be encrypted\n");
					pc_EncryptStrings = TRUE;
				}
				TK_NextToken();
				break;
			case TK_IMPORT:
				OuterImport();
				break;
			case TK_LIBRARY:
				TK_NextTokenMustBe(TK_STRING, ERR_STRING_LIT_NOT_FOUND);
				if(ImportMode == IMPORT_None)
				{
					MS_Message(MSG_DEBUG, "Allocations modified for exporting\n");
					ImportMode = IMPORT_Exporting;
				}
				else if(ImportMode == IMPORT_Importing)
				{
					PC_AddImport(tk_String);
					ExporterFlagged = YES;
				}
				TK_NextToken();
				break;
			default:
				ERR_Error(ERR_INVALID_DIRECTIVE, YES);
				TK_SkipLine();
				break;
			}
			break;
		default:
			ERR_Exit(ERR_INVALID_DECLARATOR, YES, NULL);
			break;
	   	}
	}
}

//==========================================================================
//
// OuterScript
//
//==========================================================================

static void OuterScript(void)
{
	int scriptNumber;
	symbolNode_t *sym;
	int scriptType;

	MS_Message(MSG_DEBUG, "---- OuterScript ----\n");
	BreakIndex = 0;
	CaseIndex = 0;
	StatementLevel = 0;
	ScriptVarCount = 0;
	SY_FreeLocals();
	TK_NextToken();

	if(ImportMode == IMPORT_Importing)
	{
		// When importing, the script number is not recorded, because
		// it might be a #define that is not included by the main .acs
		// file, so processing it would generate a syntax error.
		SkipBraceBlock(0);
		TK_NextToken();
		return;
	}

	// [RH] If you want to use script 0, it must be written as <<0>>.
	// This is to avoid using it accidentally, since ZDoom uses script
	// 0 to implement many of the Strife-specific line specials.

	if(tk_Token == TK_LSHIFT)
	{
		TK_NextTokenMustBe(TK_NUMBER, ERR_SCRIPT_OUT_OF_RANGE);
		if(tk_Number != 0)
		{
			ERR_Exit(ERR_SCRIPT_OUT_OF_RANGE, YES, NULL);
		}
		TK_NextTokenMustBe(TK_RSHIFT, ERR_SCRIPT_OUT_OF_RANGE);
		TK_NextToken();
		scriptNumber = 0;
	}
	else
	{
		scriptNumber = EvalConstExpression();
		if(scriptNumber < 1 || scriptNumber > 999)
		{
			TK_Undo();
			ERR_Error(ERR_SCRIPT_OUT_OF_RANGE, YES, NULL);
			SkipBraceBlock(0);
			TK_NextToken();
			return;
		}
	}
	MS_Message(MSG_DEBUG, "Script number: %d\n", scriptNumber);
	scriptType = 0;
	if(tk_Token == TK_LPAREN)
	{
		if(TK_NextToken() == TK_VOID)
		{
			TK_NextTokenMustBe(TK_RPAREN, ERR_MISSING_RPAREN);
		}
		else
		{
			TK_Undo();
			do
			{
				TK_NextTokenMustBe(TK_INT, ERR_BAD_VAR_TYPE);
				TK_NextTokenMustBe(TK_IDENTIFIER, ERR_INVALID_IDENTIFIER);
				if(ScriptVarCount == 3)
				{
					ERR_Error(ERR_TOO_MANY_SCRIPT_ARGS, YES);
				}
				if(SY_FindLocal(tk_String) != NULL)
				{ // Redefined
					ERR_Error(ERR_REDEFINED_IDENTIFIER, YES, tk_String);
				}
				else if(ScriptVarCount < 3)
				{
					sym = SY_InsertLocal(tk_String, SY_SCRIPTVAR);
					sym->info.var.index = ScriptVarCount;
					ScriptVarCount++;
				}
				TK_NextToken();
			} while(tk_Token == TK_COMMA);
			TK_TokenMustBe(TK_RPAREN, ERR_MISSING_RPAREN);
		}
		TK_NextToken();
		switch(tk_Token)
		{
		case TK_DISCONNECT:
			scriptType = DISCONNECT_SCRIPTS_BASE;
			if(ScriptVarCount != 1)
			{
				ERR_Error(ERR_DISCONNECT_NEEDS_1_ARG, YES);
			}
			break;

		case TK_OPEN:
		case TK_RESPAWN:
		case TK_DEATH:
		case TK_ENTER:
		case TK_PICKUP:
		case TK_BLUERETURN:
		case TK_REDRETURN:
		case TK_WHITERETURN:
		case TK_LIGHTNING:
		case TK_UNLOADING:
			ERR_Error(ERR_UNCLOSED_WITH_ARGS, YES);
			break;

		default:
			TK_Undo();
		}
		MS_Message(MSG_DEBUG, "Script type: %s (%d %s)\n",
			scriptType == 0 ? "closed" : "disconnect",
			ScriptVarCount, ScriptVarCount == 1 ? "arg" : "args");
	}
	else switch (tk_Token)
	{
	case TK_OPEN:
		scriptType = OPEN_SCRIPTS_BASE;
		break;

	case TK_RESPAWN:	// [BC]
		scriptType = RESPAWN_SCRIPTS_BASE;
		break;

	case TK_DEATH:		// [BC]
		scriptType = DEATH_SCRIPTS_BASE;
		break;

	case TK_ENTER:		// [BC]
		scriptType = ENTER_SCRIPTS_BASE;
		break;

	case TK_PICKUP:		// [BC]
		scriptType = PICKUP_SCRIPTS_BASE;
		break;

	case TK_BLUERETURN:	// [BC]
		scriptType = BLUE_RETURN_SCRIPTS_BASE;
		break;

	case TK_REDRETURN:	// [BC]
		scriptType = RED_RETURN_SCRIPTS_BASE;
		break;

	case TK_WHITERETURN:	// [BC]
		scriptType = WHITE_RETURN_SCRIPTS_BASE;
		break;

	case TK_LIGHTNING:
		scriptType = LIGHTNING_SCRIPTS_BASE;
		break;

	case TK_UNLOADING:
		scriptType = UNLOADING_SCRIPTS_BASE;
		break;

	case TK_DISCONNECT:
		scriptType = DISCONNECT_SCRIPTS_BASE;
		ERR_Error (ERR_DISCONNECT_NEEDS_1_ARG, YES);
		break;

	default:
		ERR_Error(ERR_BAD_SCRIPT_DECL, YES);
		SkipBraceBlock(0);
		TK_NextToken();
		return;
	}
	TK_NextToken();
	if(tk_Token == TK_NET)
	{
		scriptNumber += NET_SCRIPT_FLAG;
		TK_NextToken();
	}
	CountScript(scriptType);
	PC_AddScript(scriptNumber + scriptType, ScriptVarCount);
	pc_LastAppendedCommand = PCD_NOP;
	if(ProcessStatement(STMT_SCRIPT) == NO)
	{
		ERR_Error(ERR_INVALID_STATEMENT, YES);
	}
	if(pc_LastAppendedCommand != PCD_TERMINATE)
	{
		PC_AppendCmd(PCD_TERMINATE);
	}
	PC_SetScriptVarCount(scriptNumber + scriptType, ScriptVarCount);
	pa_ScriptCount++;
}

//==========================================================================
//
// OuterFunction
//
//==========================================================================

static void OuterFunction(void)
{
	enum ImportModes importing;
	boolean hasReturn;
	symbolNode_t *sym;
	int defLine;

	MS_Message(MSG_DEBUG, "---- OuterFunction ----\n");
	importing = ImportMode;
	BreakIndex = 0;
	CaseIndex = 0;
	StatementLevel = 0;
	ScriptVarCount = 0;
	SY_FreeLocals();
	TK_NextToken();
	if(tk_Token != TK_STR && tk_Token != TK_INT &&
		tk_Token != TK_VOID && tk_Token != TK_BOOL)
	{
		ERR_Error(ERR_BAD_RETURN_TYPE, YES);
		tk_Token = TK_VOID;
	}
	hasReturn = tk_Token != TK_VOID;
	TK_NextTokenMustBe(TK_IDENTIFIER, ERR_INVALID_IDENTIFIER);
	sym = SY_FindGlobal(tk_String);
	if(sym != NULL)
	{
		if(sym->type != SY_SCRIPTFUNC)
		{ // Redefined
			ERR_Error(ERR_REDEFINED_IDENTIFIER, YES, tk_String);
			SkipBraceBlock(0);
			TK_NextToken();
			return;
		}
		if(!sym->info.scriptFunc.predefined)
		{
			ERR_Error(ERR_FUNCTION_ALREADY_DEFINED, YES);
			ERR_ErrorAt(sym->info.scriptFunc.sourceName, sym->info.scriptFunc.sourceLine);
			ERR_Error(ERR_NONE, YES, "Previous definition was here.");
			SkipBraceBlock(0);
			TK_NextToken();
			return;
		}
		if(sym->info.scriptFunc.hasReturnValue && !hasReturn)
		{
			ERR_Error(ERR_PREVIOUS_NOT_VOID, YES);
			ERR_ErrorAt(sym->info.scriptFunc.sourceName, sym->info.scriptFunc.sourceLine);
			ERR_Error(ERR_NONE, YES, "Previous use was here.");
		}
	}
	else
	{
		sym = SY_InsertGlobal(tk_String, SY_SCRIPTFUNC);
		sym->info.scriptFunc.address = (importing == IMPORT_Importing ? 0 : pc_Address);
		sym->info.scriptFunc.predefined = NO;
	}
	defLine = tk_Line;

	TK_NextTokenMustBe(TK_LPAREN, ERR_MISSING_LPAREN);
	if(TK_NextToken() == TK_VOID)
	{
		TK_NextTokenMustBe(TK_RPAREN, ERR_MISSING_RPAREN);
	}
	else
	{
		TK_Undo();
		do
		{
			symbolNode_t *local;

			TK_NextToken();
			if(tk_Token != TK_INT && tk_Token != TK_STR && tk_Token != TK_BOOL)
			{
				ERR_Error(ERR_BAD_VAR_TYPE, YES);
				tk_Token = TK_INT;
			}
			TK_NextTokenMustBe(TK_IDENTIFIER, ERR_INVALID_IDENTIFIER);
			if(SY_FindLocal(tk_String) != NULL)
			{ // Redefined
				ERR_Error(ERR_REDEFINED_IDENTIFIER, YES, tk_String);
			}
			else
			{
				local = SY_InsertLocal(tk_String, SY_SCRIPTVAR);
				local->info.var.index = ScriptVarCount;
				ScriptVarCount++;
			}
			TK_NextToken();
		} while(tk_Token == TK_COMMA);
		TK_TokenMustBe(TK_RPAREN, ERR_MISSING_RPAREN);
	}

	sym->info.scriptFunc.sourceLine = defLine;
	sym->info.scriptFunc.sourceName = tk_SourceName;
	sym->info.scriptFunc.argCount = ScriptVarCount;
	sym->info.scriptFunc.address = (importing == IMPORT_Importing) ? 0 : pc_Address;
	sym->info.scriptFunc.hasReturnValue = hasReturn;

	if(importing == IMPORT_Importing)
	{
		SkipBraceBlock(0);
		TK_NextToken();
		sym->info.scriptFunc.predefined = NO;
		sym->info.scriptFunc.varCount = ScriptVarCount;
		return;
	}

	TK_NextToken();
	InsideFunction = sym;
	pc_LastAppendedCommand = PCD_NOP;

	// If we just call ProcessStatement(STMT_SCRIPT), and this function
	// needs to return a value but the last pcode output was not a return,
	// then the line number given in the error can be confusing because it
	// is beyond the end of the function. To avoid this, we process the
	// compound statement ourself and check if it returned something
	// before checking for the '}'. If a return is required, then the error
	// line will be shown as the one that contains the '}' (if present).

	TK_TokenMustBe(TK_LBRACE, ERR_MISSING_LBRACE);
	TK_NextToken();
	do ; while(ProcessStatement(STMT_SCRIPT) == YES);

	if(pc_LastAppendedCommand != PCD_RETURNVOID &&
	   pc_LastAppendedCommand != PCD_RETURNVAL)
	{
		if(hasReturn)
		{
			TK_Undo();
			ERR_Error(ERR_MUST_RETURN_A_VALUE, YES, NULL);
		}
		PC_AppendCmd(PCD_RETURNVOID);
	}

	TK_TokenMustBe(TK_RBRACE, ERR_INVALID_STATEMENT);
	TK_NextToken();

	sym->info.scriptFunc.predefined = NO;
	sym->info.scriptFunc.varCount = ScriptVarCount -
		sym->info.scriptFunc.argCount;
	PC_AddFunction(sym);
	UnspeculateFunction(sym);
	InsideFunction = NULL;
}

//==========================================================================
//
// OuterMapVar
//
//==========================================================================

static void OuterMapVar(void)
{
	symbolNode_t *sym = NULL;
	int index;

	MS_Message(MSG_DEBUG, "---- OuterMapVar ----\n");
	do
	{
		if(pa_MapVarCount >= MAX_MAP_VARIABLES)
		{
			ERR_Error(ERR_TOO_MANY_MAP_VARS, YES);
			index = MAX_MAP_VARIABLES;
		}
		TK_NextTokenMustBe(TK_IDENTIFIER, ERR_INVALID_IDENTIFIER);
		if(SY_FindGlobal(tk_String) != NULL)
		{ // Redefined
			ERR_Error(ERR_REDEFINED_IDENTIFIER, YES, tk_String);
			index = MAX_MAP_VARIABLES;
		}
		else
		{
			sym = SY_InsertGlobal(tk_String, SY_MAPVAR);
			if(ImportMode == IMPORT_Importing)
			{
				sym->info.var.index = index = 0;
			}
			else
			{
				sym->info.var.index = index = pa_MapVarCount;
				PC_NameMapVariable(index, sym);
				pa_MapVarCount++;
			}
		}
		TK_NextToken();
		if(tk_Token == TK_ASSIGN)
		{
			if(ImportMode != IMPORT_Importing)
			{
				TK_NextToken();
				PC_PutMapVariable (index, EvalConstExpression());
			}
			else
			{
				// When importing, skip the initializer, because we don't care.
				do
				{
					TK_NextToken();
				} while(tk_Token != TK_COMMA && tk_Token != TK_SEMICOLON);
			}
		}
		else if(tk_Token == TK_LBRACKET)
		{
			int size = 0;
			int ndim = 0;
			int dims[MAX_ARRAY_DIMS];

			memset(dims, 0, sizeof(dims));

			while(tk_Token == TK_LBRACKET)
			{
				if(ndim == MAX_ARRAY_DIMS)
				{
					ERR_Error(ERR_TOO_MANY_ARRAY_DIMS, YES);
					do
					{
						TK_NextToken();
					} while(tk_Token != TK_COMMA && tk_Token != TK_SEMICOLON);
					break;
				}
				TK_NextToken();
				if (tk_Token == TK_RBRACKET)
				{
					ERR_Error(ERR_NEED_ARRAY_SIZE, YES);
				}
				else
				{
					dims[ndim] = EvalConstExpression();
					if(dims[ndim] == 0)
					{
						ERR_Error(ERR_ZERO_DIMENSION, YES);
						dims[ndim] = 1;
					}
					if(ndim == 0)
					{
						size = dims[ndim];
					}
					else
					{
						size *= dims[ndim];
					}
				}
				ndim++;
				TK_TokenMustBe(TK_RBRACKET, ERR_MISSING_RBRACKET);
				TK_NextToken();
			}
			if(sym != NULL)
			{
				if(ImportMode != IMPORT_Importing)
				{
					PC_AddArray(index, size);
				}
				MS_Message(MSG_DEBUG, "%s changed to an array of size %d\n", sym->name, size);
				sym->type = SY_MAPARRAY;
				sym->info.array.index = index;
				sym->info.array.ndim = ndim;
				sym->info.array.size = size;
				if(ndim > 0)
				{
					int i;

					sym->info.array.dimensions[ndim-1] = 1;
					for(i = ndim - 2; i >= 0; --i)
					{
						sym->info.array.dimensions[i] =
							sym->info.array.dimensions[i+1] * dims[i+1];
					}
				}
				if(tk_Token == TK_ASSIGN)
				{
					InitializeArray(sym, dims, size);
				}
			}
		}
	} while(tk_Token == TK_COMMA);
	TK_TokenMustBe(TK_SEMICOLON, ERR_MISSING_SEMICOLON);
	TK_NextToken();
}

//==========================================================================
//
// OuterWorldVar
//
//==========================================================================

static void OuterWorldVar(boolean isGlobal)
{
	int index;
	symbolNode_t *sym;

	MS_Message(MSG_DEBUG, "---- Outer%sVar ----\n", isGlobal ? "Global" : "World");
	if(TK_NextToken() != TK_INT)
	{
		if(tk_Token != TK_BOOL)
		{
			TK_TokenMustBe(TK_STR, ERR_BAD_VAR_TYPE);
		}
	}
	do
	{
		TK_NextTokenMustBe(TK_NUMBER, ERR_MISSING_WVAR_INDEX);
		if(tk_Number >= (isGlobal ? MAX_GLOBAL_VARIABLES : MAX_WORLD_VARIABLES))
		{
			ERR_Error(ERR_BAD_WVAR_INDEX+isGlobal, YES);
			index = 0;
		}
		else
		{
			index = tk_Number;
		}
		TK_NextTokenMustBe(TK_COLON, ERR_MISSING_WVAR_COLON+isGlobal);
		TK_NextTokenMustBe(TK_IDENTIFIER, ERR_INVALID_IDENTIFIER);
		if(SY_FindGlobal(tk_String) != NULL)
		{ // Redefined
			ERR_Error(ERR_REDEFINED_IDENTIFIER, YES, tk_String);
		}
		else
		{
			TK_NextToken();
			if(tk_Token == TK_LBRACKET)
			{
				TK_NextToken ();
				if(tk_Token != TK_RBRACKET)
				{
					ERR_Error(ERR_NO_NEED_ARRAY_SIZE, YES);
					TK_SkipPast(TK_RBRACKET);
				}
				else
				{
					TK_NextToken();
				}
				if(tk_Token == TK_LBRACKET)
				{
					ERR_Error(ERR_NO_MULTIDIMENSIONS, YES);
					do
					{
						TK_SkipPast(TK_RBRACKET);
					}
					while(tk_Token == TK_LBRACKET);
				}
				sym = SY_InsertGlobal(tk_String, isGlobal ? SY_GLOBALARRAY : SY_WORLDARRAY);
				sym->info.var.index = index;
				if (isGlobal)
					pa_GlobalArrayCount++;
				else
					pa_WorldArrayCount++;
			}
			else
			{
				sym = SY_InsertGlobal(tk_String, isGlobal ? SY_GLOBALVAR : SY_WORLDVAR);
				sym->info.var.index = index;
				if (isGlobal)
					pa_GlobalVarCount++;
				else
					pa_WorldVarCount++;
			}
		}
	} while(tk_Token == TK_COMMA);
	TK_TokenMustBe(TK_SEMICOLON, ERR_MISSING_SEMICOLON);
	TK_NextToken();
}

//==========================================================================
//
// OuterSpecialDef
//
//==========================================================================

static void OuterSpecialDef(void)
{
	int special;
	symbolNode_t *sym;

	MS_Message(MSG_DEBUG, "---- OuterSpecialDef ----\n");
	if(ImportMode == IMPORT_Importing)
	{
		// No need to process special definitions when importing.
		TK_SkipPast(TK_SEMICOLON);
	}
	else
	{
		do
		{
			TK_NextTokenMustBe(TK_NUMBER, ERR_MISSING_SPEC_VAL);
			special = tk_Number;
			TK_NextTokenMustBe(TK_COLON, ERR_MISSING_SPEC_COLON);
			TK_NextTokenMustBe(TK_IDENTIFIER, ERR_INVALID_IDENTIFIER);
			sym = SY_InsertGlobalUnique(tk_String, SY_SPECIAL);
			TK_NextTokenMustBe(TK_LPAREN, ERR_MISSING_LPAREN);
			TK_NextTokenMustBe(TK_NUMBER, ERR_MISSING_SPEC_ARGC);
			sym->info.special.value = special;
			sym->info.special.argCount = tk_Number | (tk_Number << 16);
			TK_NextToken();
			if(tk_Token == TK_COMMA)
			{ // Get maximum arg count
				TK_NextTokenMustBe(TK_NUMBER, ERR_MISSING_SPEC_ARGC);
				sym->info.special.argCount =
					(sym->info.special.argCount & 0xffff) | (tk_Number << 16);
			}
			else
			{
				TK_Undo ();
			}
			TK_NextTokenMustBe(TK_RPAREN, ERR_MISSING_RPAREN);
			TK_NextToken();
		} while(tk_Token == TK_COMMA);
		TK_TokenMustBe(TK_SEMICOLON, ERR_MISSING_SEMICOLON);
		TK_NextToken();
	}
}

//==========================================================================
//
// OuterLocalizedStrings
//
//==========================================================================

static void OuterLocalizedStrings(void)
{
	int language = 0;	// Shut up, GCC
	int strIndex;

	MS_Message(MSG_DEBUG, "---- OuterLocalizedStrings ----\n");
	if(ImportMode == IMPORT_Importing)
	{ // We don't care about localizations when importing, because the
	  // importer has no way of accessing them.
		SkipBraceBlock(0);
		TK_NextToken();
		return;
	}

	TK_NextTokenMustBe(TK_IDENTIFIER, ERR_MISSING_LANGCODE);

	if(strlen(tk_String) != 3 && strlen(tk_String) != 2)
	{
		ERR_Error(ERR_LANGCODE_SIZE, YES);
		SkipBraceBlock(0);
		TK_NextToken();
		return;
	}
	tk_String[0] = tolower(tk_String[0]);
	tk_String[1] = tolower(tk_String[1]);
	tk_String[2] = tolower(tk_String[2]);
	if (tk_String[0] < 'a' || tk_String[0] > 'z' ||
		tk_String[1] < 'a' || tk_String[1] > 'z' ||
		(tk_String[2] && (tk_String[2] < 'a' || tk_String[2] > 'z')))
	{
		ERR_Error(ERR_BAD_LANGCODE, YES);
		SkipBraceBlock(0);
		TK_NextToken();
		return;
	}
	if (ImportMode != IMPORT_Importing)
	{
		language = STR_FindLanguage(tk_String);
		MS_Message(MSG_DEBUG, "Language %s (%d)\n", tk_String, language);
	}

	TK_NextToken();
	if(tk_Token != TK_LBRACE)
	{
		ERR_Error(ERR_MISSING_LBRACE_LOC, YES);
		SkipBraceBlock(0);
		TK_NextToken();
		return;
	}
	TK_NextToken();

	while (tk_Token == TK_STRING)
	{
		strIndex = STR_Find(tk_String);
		TK_NextTokenMustBe(TK_ASSIGN, ERR_MISSING_ASSIGN_OP);
		TK_NextTokenMustBe(TK_STRING, ERR_MISSING_LOCALIZED);
		if (ImportMode != IMPORT_Importing)
		{
			STR_PutStringInLanguage(language, strIndex, tk_String);
		}
		TK_NextToken();
	}

	TK_TokenMustBe(TK_RBRACE, ERR_MISSING_RBRACE_LOC);
	TK_NextToken();
}

//==========================================================================
//
// OuterDefine
//
//==========================================================================

static void OuterDefine(boolean force)
{
	int value;
	symbolNode_t *sym;

	// Don't define inside an import
	if(ImportMode != IMPORT_Importing || force)
	{
		MS_Message(MSG_DEBUG, "---- OuterDefine %s----\n",
			force ? "(forced) " : "");
		TK_NextTokenMustBe(TK_IDENTIFIER, ERR_INVALID_IDENTIFIER);
		sym = SY_InsertGlobalUnique(tk_String, SY_CONSTANT);
		TK_NextToken();
		value = EvalConstExpression();
		MS_Message(MSG_DEBUG, "Constant value: %d\n", value);
		sym->info.constant.value = value;
	}
	else
	{
		TK_NextToken();
		TK_NextToken();
		EvalConstExpression();
	}
}

//==========================================================================
//
// OuterInclude
//
//==========================================================================

static void OuterInclude(void)
{
	// Don't include inside an import
	if(ImportMode != IMPORT_Importing)
	{
		MS_Message(MSG_DEBUG, "---- OuterInclude ----\n");
		TK_NextTokenMustBe(TK_STRING, ERR_STRING_LIT_NOT_FOUND);
		TK_Include(tk_String);
	}
	else
	{
		TK_NextToken();
	}
	TK_NextToken();
}

//==========================================================================
//
// OuterImport
//
//==========================================================================

static void OuterImport(void)
{

	MS_Message(MSG_DEBUG, "---- OuterImport ----\n");
	if(ImportMode == IMPORT_Importing)
	{
		// Don't import inside an import
		TK_NextToken();
	}
	else
	{
		MS_Message(MSG_DEBUG, "Importing a file\n");
		TK_NextTokenMustBe(TK_STRING, ERR_STRING_LIT_NOT_FOUND);
		TK_Import(tk_String, ImportMode);
	}
	TK_NextToken();
}

//==========================================================================
//
// ProcessStatement
//
//==========================================================================

static boolean ProcessStatement(statement_t owner)
{
	if(StatementIndex == MAX_STATEMENT_DEPTH)
	{
		ERR_Exit(ERR_STATEMENT_OVERFLOW, YES);
	}
	StatementHistory[StatementIndex++] = owner;
	switch(tk_Token)
	{
		case TK_INT:
		case TK_STR:
		case TK_BOOL:
			LeadingVarDeclare();
			break;
		case TK_LINESPECIAL:
			LeadingLineSpecial(NO);
			break;
		case TK_ACSEXECUTEWAIT:
			tk_SpecialArgCount = 1 | (5<<16);
			tk_SpecialValue = 80;
			LeadingLineSpecial(YES);
			break;
		case TK_RESTART:
			LeadingRestart();
			break;
		case TK_SUSPEND:
			LeadingSuspend();
			break;
		case TK_TERMINATE:
			LeadingTerminate();
			break;
		case TK_RETURN:
			LeadingReturn();
			break;
		case TK_IDENTIFIER:
			LeadingIdentifier();
			break;
		case TK_PRINT:
		case TK_PRINTBOLD:
			LeadingPrint();
			break;
		case TK_HUDMESSAGE:
		case TK_HUDMESSAGEBOLD:
			LeadingHudMessage();
			break;
		case TK_IF:
			LeadingIf();
			break;
		case TK_FOR:
			LeadingFor();
			break;
		case TK_WHILE:
		case TK_UNTIL:
			LeadingWhileUntil();
			break;
		case TK_DO:
			LeadingDo();
			break;
		case TK_SWITCH:
			LeadingSwitch();
			break;
		case TK_CASE:
			if(owner != STMT_SWITCH)
			{
				ERR_Error(ERR_CASE_NOT_IN_SWITCH, YES);
				TK_SkipPast(TK_COLON);
			}
			else
			{
				LeadingCase();
			}
			break;
		case TK_DEFAULT:
			if(owner != STMT_SWITCH)
			{
				ERR_Error(ERR_DEFAULT_NOT_IN_SWITCH, YES);
				TK_SkipPast(TK_COLON);
			}
			else if(DefaultInCurrent() == YES)
			{
				ERR_Error(ERR_MULTIPLE_DEFAULT, YES);
				TK_SkipPast(TK_COLON);
			}
			else
			{
				LeadingDefault();
			}
			break;
		case TK_BREAK:
			if(BreakAncestor() == NO)
			{
				ERR_Error(ERR_MISPLACED_BREAK, YES);
				TK_SkipPast(TK_SEMICOLON);
			}
			else
			{
				LeadingBreak();
			}
			break;
		case TK_CONTINUE:
			if(ContinueAncestor() == NO)
			{
				ERR_Error(ERR_MISPLACED_CONTINUE, YES);
				TK_SkipPast(TK_SEMICOLON);
			}
			else
			{
				LeadingContinue();
			}
			break;
		case TK_CREATETRANSLATION:
			LeadingCreateTranslation();
			break;
		case TK_LBRACE:
			LeadingCompoundStatement(owner);
			break;
		case TK_SEMICOLON:
			TK_NextToken();
			break;
		case TK_INC:
		case TK_DEC:
			LeadingIncDec(tk_Token);
			break;
		default:
			StatementIndex--;
			return NO;
			break;
	}
	StatementIndex--;
	return YES;
}

//==========================================================================
//
// LeadingCompoundStatement
//
//==========================================================================

static void LeadingCompoundStatement(statement_t owner)
{
	StatementLevel += AdjustStmtLevel[owner];
	TK_NextToken(); // Eat the TK_LBRACE
	do ; while(ProcessStatement(owner) == YES);
	TK_TokenMustBe(TK_RBRACE, ERR_INVALID_STATEMENT);
	TK_NextToken();
	StatementLevel -= AdjustStmtLevel[owner];
}

//==========================================================================
//
// LeadingVarDeclare
//
//==========================================================================

static void LeadingVarDeclare(void)
{
	symbolNode_t *sym = NULL;

	MS_Message(MSG_DEBUG, "---- LeadingVarDeclare ----\n");
	do
	{
		TK_NextTokenMustBe(TK_IDENTIFIER, ERR_INVALID_IDENTIFIER);
#if 0
		if(ScriptVarCount == MAX_SCRIPT_VARIABLES)
		{
			ERR_Error(InsideFunction
				? ERR_TOO_MANY_FUNCTION_VARS
				: ERR_TOO_MANY_SCRIPT_VARS,
				YES, NULL);
			ScriptVarCount++;
		}
		else
#endif 
		if(SY_FindLocal(tk_String) != NULL)
		{ // Redefined
			ERR_Error(ERR_REDEFINED_IDENTIFIER, YES, tk_String);
		}
		else
		{
			sym = SY_InsertLocal(tk_String, SY_SCRIPTVAR);
			sym->info.var.index = ScriptVarCount;
			ScriptVarCount++;
		}
		TK_NextToken();
		if(tk_Token == TK_LBRACKET)
		{
			ERR_Error(ERR_ARRAY_MAPVAR_ONLY, YES);
			do ; while(TK_NextToken() != TK_COMMA && tk_Token != TK_SEMICOLON);
		}
		else if(tk_Token == TK_ASSIGN)
		{
			TK_NextToken();
			EvalExpression();
			if(sym != NULL)
			{
				PC_AppendCmd(PCD_ASSIGNSCRIPTVAR);
				PC_AppendShrink(sym->info.var.index);
			}
		}
	} while(tk_Token == TK_COMMA);
	TK_TokenMustBe(TK_SEMICOLON, ERR_MISSING_SEMICOLON);
	TK_NextToken();
}

//==========================================================================
//
// LeadingLineSpecial
//
//==========================================================================

static void LeadingLineSpecial(boolean executewait)
{
	int i;
	int argCount;
	int argCountMin;
	int argCountMax;
	int argSave[8];
	U_BYTE specialValue;
	boolean direct;

	MS_Message(MSG_DEBUG, "---- LeadingLineSpecial ----\n");
	argCountMin = tk_SpecialArgCount & 0xffff;
	argCountMax = tk_SpecialArgCount >> 16;
	specialValue = tk_SpecialValue;
	TK_NextTokenMustBe(TK_LPAREN, ERR_MISSING_LPAREN);
	i = 0;
	if(argCountMax > 0)
	{
		if(TK_NextToken() == TK_CONST)
		{
			TK_NextTokenMustBe(TK_COLON, ERR_MISSING_COLON);
			direct = YES;
		}
		else
		{
			TK_Undo();
			direct = NO;
		}
		do
		{
			if(i == argCountMax)
			{
				ERR_Error(ERR_BAD_LSPEC_ARG_COUNT, YES);
				i = argCountMax+1;
			}
			TK_NextToken();
			if(direct == YES)
			{
				argSave[i] = EvalConstExpression();
			}
			else
			{
				EvalExpression();
				if (i == 0 && executewait)
				{
					PC_AppendCmd(PCD_DUP);
				}
			}
			if(i < argCountMax)
			{
				i++;
			}
		} while(tk_Token == TK_COMMA);
		if(i < argCountMin)
		{
			ERR_Error(ERR_BAD_LSPEC_ARG_COUNT, YES);
			TK_SkipPast(TK_SEMICOLON);
			return;
		}
		argCount = i;
	}
	else
	{
		// [RH] I added some zero-argument specials without realizing that
		// ACS won't allow for less than one, so fake them as one-argument
		// specials with a parameter of 0.
		argCount = 1;
		direct = YES;
		argSave[0] = 0;
		TK_NextToken ();
	}
	TK_TokenMustBe(TK_RPAREN, ERR_MISSING_RPAREN);
	TK_NextTokenMustBe(TK_SEMICOLON, ERR_MISSING_SEMICOLON);
	if(direct == NO)
	{
		PC_AppendCmd(PCD_LSPEC1+(argCount-1));
		if(pc_NoShrink)
		{
			PC_AppendLong(specialValue);
		}
		else
		{
			PC_AppendByte(specialValue);
		}
		if(executewait)
		{
			PC_AppendCmd(PCD_SCRIPTWAIT);
		}
	}
	else
	{
		boolean uselongform;

		if(pc_NoShrink)
		{
			PC_AppendCmd(PCD_LSPEC1DIRECT+(argCount-1));
			PC_AppendLong(specialValue);
			uselongform = YES;
		}
		else
		{
			uselongform = NO;
			for (i = 0; i < argCount; i++)
			{
				if ((unsigned int)argSave[i] > 255)
				{
					uselongform = YES;
					break;
				}
			}
			PC_AppendCmd((argCount-1)+(uselongform?PCD_LSPEC1DIRECT:PCD_LSPEC1DIRECTB));
			PC_AppendByte(specialValue);
		}
		if (uselongform)
		{
			for (i = 0; i < argCount; i++)
			{
				PC_AppendLong(argSave[i]);
			}
		}
		else
		{
			for (i = 0; i < argCount; i++)
			{
				PC_AppendByte((U_BYTE)argSave[i]);
			}
		}
		if(executewait)
		{
			PC_AppendCmd(PCD_SCRIPTWAITDIRECT);
			PC_AppendLong(argSave[0]);
		}
	}
	TK_NextToken();
}

//==========================================================================
//
// LeadingIdentifier
//
//==========================================================================

static void LeadingIdentifier(void)
{
	symbolNode_t *sym;

	sym = SpeculateSymbol(tk_String, NO);
	switch(sym->type)
	{
		case SY_MAPARRAY:
		case SY_SCRIPTVAR:
		case SY_MAPVAR:
		case SY_WORLDVAR:
		case SY_GLOBALVAR:
		case SY_WORLDARRAY:
		case SY_GLOBALARRAY:
			LeadingVarAssign(sym);
			break;
		case SY_INTERNFUNC:
			LeadingInternFunc(sym);
			break;
		case SY_SCRIPTFUNC:
			LeadingScriptFunc(sym);
			break;
		default:
			break;
	}
}

//==========================================================================
//
// LeadingInternFunc
//
//==========================================================================

static void LeadingInternFunc(symbolNode_t *sym)
{
	if(InsideFunction && sym->info.internFunc.latent)
	{
		ERR_Error(ERR_LATENT_IN_FUNC, YES);
	}
	ProcessInternFunc(sym);
	if(sym->info.internFunc.hasReturnValue == YES)
	{
		PC_AppendCmd(PCD_DROP);
	}
	TK_TokenMustBe(TK_SEMICOLON, ERR_MISSING_SEMICOLON);
	TK_NextToken();
}

//==========================================================================
//
// ProcessInternFunc
//
//==========================================================================

static void ProcessInternFunc(symbolNode_t *sym)
{
	int i;
	int argCount;
	int optMask;
	int outMask;
	boolean direct;
	boolean specialDirect;
	int argSave[8];

	MS_Message(MSG_DEBUG, "---- ProcessInternFunc ----\n");
	argCount = sym->info.internFunc.argCount;
	optMask = sym->info.internFunc.optMask;
	outMask = sym->info.internFunc.outMask;
	TK_NextTokenMustBe(TK_LPAREN, ERR_MISSING_LPAREN);
	if(TK_NextToken() == TK_CONST)
	{
		TK_NextTokenMustBe(TK_COLON, ERR_MISSING_COLON);
		if(sym->info.internFunc.directCommand == PCD_NOP)
		{
			ERR_Error(ERR_NO_DIRECT_VER, YES, NULL);
			direct = NO;
			specialDirect = NO;
		}
		else
		{
			direct = YES;
			if (pc_NoShrink || argCount > 2 ||
				(sym->info.internFunc.directCommand != PCD_DELAYDIRECT &&
				 sym->info.internFunc.directCommand != PCD_RANDOMDIRECT))
			{
				specialDirect = NO;
				PC_AppendCmd(sym->info.internFunc.directCommand);
			}
			else
			{
				specialDirect = YES;
			}
		}
		TK_NextToken();
	}
	else
	{
		direct = NO;
		specialDirect = NO;	// keep GCC quiet
	}
	i = 0;
	if(argCount > 0)
	{
		if(tk_Token == TK_RPAREN)
		{
			ERR_Error(ERR_MISSING_PARAM, YES);
		}
		else
		{
			TK_Undo(); // Adjust for first expression
			do
			{
				if(i == argCount)
				{
					ERR_Error(ERR_BAD_ARG_COUNT, YES);
					TK_SkipTo(TK_SEMICOLON);
					TK_Undo();
					return;
				}
				TK_NextToken();
				if(direct == YES)
				{
					if (tk_Token != TK_COMMA)
					{
						if (specialDirect)
						{
							argSave[i] = EvalConstExpression();
						}
						else
						{
							PC_AppendLong(EvalConstExpression());
						}
					}
					else
					{
						if (optMask & 1)
						{
							if (specialDirect)
							{
								argSave[i] = 0;
							}
							else
							{
								PC_AppendLong(0);
							}
						}
						else
						{
							ERR_Error(ERR_MISSING_PARAM, YES);
						}
					}
				}
				else
				{
					if (tk_Token != TK_COMMA)
					{
						if (!(outMask & 1))
						{
							EvalExpression();
						}
						else if (tk_Token != TK_IDENTIFIER)
						{
							ERR_Error (ERR_PARM_MUST_BE_VAR, YES);
							do
							{
								TK_NextToken();
							} while (tk_Token != TK_COMMA && tk_Token != TK_RPAREN);
						}
						else
						{
							symbolNode_t *sym = DemandSymbol (tk_String);
							PC_AppendCmd (PCD_PUSHNUMBER);
							switch (sym->type)
							{
							case SY_SCRIPTVAR:
								PC_AppendLong(sym->info.var.index | OUTVAR_SCRIPT_SPEC);
								break;
							case SY_MAPVAR:
								PC_AppendLong(sym->info.var.index | OUTVAR_MAP_SPEC);
								break;
							case SY_WORLDVAR:
								PC_AppendLong(sym->info.var.index | OUTVAR_WORLD_SPEC);
								break;
							case SY_GLOBALVAR:
								PC_AppendLong(sym->info.var.index | OUTVAR_GLOBAL_SPEC);
								break;
							default:
								ERR_Error (ERR_PARM_MUST_BE_VAR, YES);
								do
								{
									TK_NextToken();
								} while (tk_Token != TK_COMMA && tk_Token != TK_RPAREN);
								break;
							}
							TK_NextToken ();
						}
					}
					else
					{
						if (optMask & 1)
						{
							PC_AppendPushVal(0);
						}
						else
						{
							ERR_Error(ERR_MISSING_PARAM, YES);
						}
					}
				}
				i++;
				optMask >>= 1;
				outMask >>= 1;
			} while(tk_Token == TK_COMMA);
		}
	}
	while (i < argCount && (optMask & 1))
	{
		if (direct == YES)
		{
			if (specialDirect)
			{
				argSave[i] = 0;
			}
			else
			{
				PC_AppendLong(0);
			}
		}
		else
		{
			PC_AppendPushVal(0);
		}
		i++;
		optMask >>= 1;
	}
	if(i != argCount && i > 0)
	{
		ERR_Error(ERR_BAD_ARG_COUNT, YES);
	}
	TK_TokenMustBe(TK_RPAREN, argCount > 0 ? ERR_MISSING_RPAREN : ERR_BAD_ARG_COUNT);
	if(direct == NO)
	{
		PC_AppendCmd(sym->info.internFunc.stackCommand);
	}
	else if (specialDirect)
	{
		boolean uselongform = NO;
		pcd_t shortpcd;

		switch (sym->info.internFunc.directCommand)
		{
		case PCD_DELAYDIRECT:
			shortpcd = PCD_DELAYDIRECTB;
			break;
		case PCD_RANDOMDIRECT:
			shortpcd = PCD_RANDOMDIRECTB;
			break;
		default:
			uselongform = YES;
			shortpcd = PCD_NOP;
			break;
		}

		if (!uselongform)
		{
			for (i = 0; i < argCount; i++)
			{
				if ((unsigned int)argSave[i] > 255)
				{
					uselongform = YES;
					break;
				}
			}
		}

		if (uselongform)
		{
			PC_AppendCmd(sym->info.internFunc.directCommand);
			for (i = 0; i < argCount; i++)
			{
				PC_AppendLong (argSave[i]);
			}
		}
		else
		{
			PC_AppendCmd (shortpcd);
			for (i = 0; i < argCount; i++)
			{
				PC_AppendByte ((U_BYTE)argSave[i]);
			}
		}
	}
	TK_NextToken();
}

//==========================================================================
//
// LeadingScriptFunc
//
//==========================================================================

static void LeadingScriptFunc(symbolNode_t *sym)
{
	ProcessScriptFunc(sym, YES);
	TK_TokenMustBe(TK_SEMICOLON, ERR_MISSING_SEMICOLON);
	TK_NextToken();
}


//==========================================================================
//
// ProcessScriptFunc
//
//==========================================================================

static void ProcessScriptFunc(symbolNode_t *sym, boolean discardReturn)
{
	int i;
	int argCount;

	MS_Message(MSG_DEBUG, "---- ProcessScriptFunc ----\n");
	argCount = sym->info.scriptFunc.argCount;
	TK_NextTokenMustBe(TK_LPAREN, ERR_MISSING_LPAREN);
	i = 0;
	if(argCount == 0)
	{
		TK_NextTokenMustBe(TK_RPAREN, ERR_BAD_ARG_COUNT);
	}
	else if(argCount > 0)
	{
		TK_NextToken();
		if(tk_Token == TK_RPAREN)
		{
			ERR_Error(ERR_BAD_ARG_COUNT, YES);
			TK_SkipTo(TK_SEMICOLON);
			return;
		}
		TK_Undo();
		do
		{
			if(i == argCount)
			{
				ERR_Error(ERR_BAD_ARG_COUNT, YES);
				TK_SkipTo(TK_SEMICOLON);
				return;
			}
			TK_NextToken();
			if (tk_Token != TK_COMMA)
			{
				EvalExpression();
			}
			else
			{
				ERR_Error(ERR_MISSING_PARAM, YES);
				TK_SkipTo(TK_SEMICOLON);
				return;
			}
			i++;
		} while(tk_Token == TK_COMMA);
	}
	if(argCount < 0)
	{ // Function has not been defined yet, so assume arg count is correct
		TK_NextToken();
		while (tk_Token != TK_RPAREN)
		{
			EvalExpression();
			i++;
			if (tk_Token == TK_COMMA)
			{
				TK_NextToken();
			}
			else if (tk_Token != TK_RPAREN)
			{
				ERR_Error(ERR_MISSING_PARAM, YES);
				TK_SkipTo(TK_SEMICOLON);
				return;
			}
		}
	}
	else if(i != argCount)
	{
		ERR_Error(ERR_BAD_ARG_COUNT, YES);
		TK_SkipTo(TK_SEMICOLON);
		return;
	}
	TK_TokenMustBe(TK_RPAREN, ERR_MISSING_RPAREN);
	PC_AppendCmd(discardReturn ? PCD_CALLDISCARD : PCD_CALL);
	if(sym->info.scriptFunc.predefined && ImportMode != IMPORT_Importing)
	{
		AddScriptFuncRef(sym, pc_Address, i);
	}
	if (pc_NoShrink)
	{
		PC_AppendLong(sym->info.scriptFunc.funcNumber);
	}
	else
	{
		PC_AppendByte((U_BYTE)sym->info.scriptFunc.funcNumber);
	}
	TK_NextToken();
}

//==========================================================================
//
// BuildPrintString
//
//==========================================================================

static void BuildPrintString(void)
{
	pcd_t printCmd;

	do
	{
		switch(TK_NextCharacter())
		{
			case 's': // string
				printCmd = PCD_PRINTSTRING;
				break;
			case 'l': // [RH] localized string
				printCmd = PCD_PRINTLOCALIZED;
				break;
			case 'i': // integer
			case 'd': // decimal
				printCmd = PCD_PRINTNUMBER;
				break;
			case 'c': // character
				printCmd = PCD_PRINTCHARACTER;
				break;
			case 'n': // [BC] name
				printCmd = PCD_PRINTNAME;
				break;
			case 'f': // [RH] fixed point
				printCmd = PCD_PRINTFIXED;
				break;
			default:
				printCmd = PCD_PRINTSTRING;
				ERR_Error(ERR_UNKNOWN_PRTYPE, YES);
				break;
		}
		TK_NextTokenMustBe(TK_COLON, ERR_MISSING_COLON);
		TK_NextToken();
		EvalExpression();
		PC_AppendCmd(printCmd);
	} while(tk_Token == TK_COMMA);
}

//==========================================================================
//
// LeadingPrint
//
//==========================================================================

static void LeadingPrint(void)
{
	tokenType_t stmtToken;

	MS_Message(MSG_DEBUG, "---- LeadingPrint ----\n");
	stmtToken = tk_Token; // Will be TK_PRINT or TK_PRINTBOLD
	PC_AppendCmd(PCD_BEGINPRINT);
	TK_NextTokenMustBe(TK_LPAREN, ERR_MISSING_LPAREN);
	BuildPrintString();
	TK_TokenMustBe(TK_RPAREN, ERR_MISSING_RPAREN);
	if(stmtToken == TK_PRINT)
	{
		PC_AppendCmd(PCD_ENDPRINT);
	}
	else
	{
		PC_AppendCmd(PCD_ENDPRINTBOLD);
	}
	TK_NextTokenMustBe(TK_SEMICOLON, ERR_MISSING_SEMICOLON);
	TK_NextToken();
}

//==========================================================================
//
// LeadingHudMessage
//
// hudmessage(str text; int type, int id, int color, fixed x, fixed y, fixed holdtime, ...)
//
//==========================================================================

static void LeadingHudMessage(void)
{
	tokenType_t stmtToken;
	int i;

	MS_Message(MSG_DEBUG, "---- LeadingHudMessage ----\n");
	stmtToken = tk_Token; // Will be TK_HUDMESSAGE or TK_HUDMESSAGEBOLD
	PC_AppendCmd(PCD_BEGINPRINT);
	TK_NextTokenMustBe(TK_LPAREN, ERR_MISSING_LPAREN);
	BuildPrintString();
	TK_TokenMustBe(TK_SEMICOLON, ERR_MISSING_PARAM);
	PC_AppendCmd(PCD_MOREHUDMESSAGE);
	for (i = 6; i > 0; i--)
	{
		TK_NextToken();
		EvalExpression();
		if (i > 1)
			TK_TokenMustBe(TK_COMMA, ERR_MISSING_PARAM);
	}
	if (tk_Token == TK_COMMA)
	{ // HUD message has optional parameters
		PC_AppendCmd(PCD_OPTHUDMESSAGE);
		do
		{
			TK_NextToken();
			EvalExpression();
		} while (tk_Token == TK_COMMA);
	}
	TK_TokenMustBe(TK_RPAREN, ERR_MISSING_RPAREN);
	PC_AppendCmd(stmtToken == TK_HUDMESSAGE ? 
		PCD_ENDHUDMESSAGE : PCD_ENDHUDMESSAGEBOLD);
	TK_NextTokenMustBe(TK_SEMICOLON, ERR_MISSING_SEMICOLON);
	TK_NextToken();
}

//==========================================================================
//
// LeadingCreateTranslation
//
// Simple grammar:
//
// tranlationstmt: CreateTranslation ( exp opt_args ) ;
// opt_args: /* empty: just reset the translation */ | , arglist
// arglist: arg | arglist arg
// arg: range = replacement
// range: exp : exp
// replacement: palrep | colorrep
// palrep: exp : exp
// colorrep: [exp,exp,exp]:[exp,exp,exp]
//==========================================================================

static void LeadingCreateTranslation(void)
{
	MS_Message(MSG_DEBUG, "---- LeadingCreateTranslation ----\n");
	TK_NextTokenMustBe(TK_LPAREN, ERR_MISSING_LPAREN);
	TK_NextToken();
	EvalExpression();
	PC_AppendCmd(PCD_STARTTRANSLATION);
	while (tk_Token == TK_COMMA)
	{
		TK_NextToken();
		EvalExpression();	// Get first palette entry in range
		TK_TokenMustBe(TK_COLON, ERR_MISSING_COLON);
		TK_NextToken();
		EvalExpression();	// Get second palette entry in range
		TK_TokenMustBe(TK_ASSIGN, ERR_MISSING_ASSIGN);

		TK_NextToken();
		if(tk_Token == TK_LBRACKET)
		{ // Replacement is color range
			int i, j;

			TK_NextToken();

			for(j = 2; j != 0; --j)
			{
				for(i = 3; i != 0; --i)
				{
					EvalExpression();
					if(i != 1)
					{
						TK_TokenMustBe(TK_COMMA, ERR_MISSING_COMMA);
					}
					else
					{
						TK_TokenMustBe(TK_RBRACKET, ERR_MISSING_RBRACKET);
					}
					TK_NextToken();
				}
				if(j == 2)
				{
					TK_TokenMustBe(TK_COLON, ERR_MISSING_COLON);
					TK_NextTokenMustBe(TK_LBRACKET, ERR_MISSING_LBRACKET);
					TK_NextToken();
				}
			}
			PC_AppendCmd(PCD_TRANSLATIONRANGE2);
		}
		else
		{ // Replacement is palette range
			EvalExpression();
			TK_TokenMustBe(TK_COLON, ERR_MISSING_COLON);
			TK_NextToken();
			EvalExpression();
			PC_AppendCmd(PCD_TRANSLATIONRANGE1);
		}
	}
	PC_AppendCmd(PCD_ENDTRANSLATION);
	TK_TokenMustBe(TK_RPAREN, ERR_MISSING_RPAREN);
	TK_NextTokenMustBe(TK_SEMICOLON, ERR_MISSING_SEMICOLON);
	TK_NextToken();
}

//==========================================================================
//
// LeadingIf
//
//==========================================================================

static void LeadingIf(void)
{
	int jumpAddrPtr1;
	int jumpAddrPtr2;

	MS_Message(MSG_DEBUG, "---- LeadingIf ----\n");
	TK_NextTokenMustBe(TK_LPAREN, ERR_MISSING_LPAREN);
	TK_NextToken();
	EvalExpression();
	TK_TokenMustBe(TK_RPAREN, ERR_MISSING_RPAREN);
	PC_AppendCmd(PCD_IFNOTGOTO);
	jumpAddrPtr1 = pc_Address;
	PC_SkipLong();
	TK_NextToken();
	if(ProcessStatement(STMT_IF) == NO)
	{
		ERR_Error(ERR_INVALID_STATEMENT, YES);
	}
	if(tk_Token == TK_ELSE)
	{
		PC_AppendCmd(PCD_GOTO);
		jumpAddrPtr2 = pc_Address;
		PC_SkipLong();
		PC_WriteLong(pc_Address, jumpAddrPtr1);
		TK_NextToken();
		if(ProcessStatement(STMT_ELSE) == NO)
		{
			ERR_Error(ERR_INVALID_STATEMENT, YES);
		}
		PC_WriteLong(pc_Address, jumpAddrPtr2);
	}
	else
	{
		PC_WriteLong(pc_Address, jumpAddrPtr1);
	}
}

//==========================================================================
//
// LeadingFor
//
//==========================================================================

static void LeadingFor(void)
{
	int exprAddr;
	int incAddr;
	int ifgotoAddr;
	int	gotoAddr;

	MS_Message(MSG_DEBUG, "---- LeadingFor ----\n");
	TK_NextTokenMustBe(TK_LPAREN, ERR_MISSING_LPAREN);
	TK_NextToken();
	if(ProcessStatement(STMT_FOR) == NO)
	{
		ERR_Error(ERR_INVALID_STATEMENT, YES);
	}
	exprAddr = pc_Address;
	EvalExpression();
	TK_TokenMustBe(TK_SEMICOLON, ERR_MISSING_SEMICOLON);
	TK_NextToken();
	PC_AppendCmd(PCD_IFGOTO);
	ifgotoAddr = pc_Address;
	PC_SkipLong();
	PC_AppendCmd(PCD_GOTO);
	gotoAddr = pc_Address;
	PC_SkipLong();
	incAddr = pc_Address;
	forSemicolonHack = TRUE;
	if(ProcessStatement(STMT_FOR) == NO)
	{
		ERR_Error(ERR_INVALID_STATEMENT, YES);
	}
	forSemicolonHack = FALSE;
	PC_AppendCmd(PCD_GOTO);
	PC_AppendLong(exprAddr);
	PC_WriteLong(pc_Address,ifgotoAddr);
	if(ProcessStatement(STMT_FOR) == NO)
	{
		ERR_Error(ERR_INVALID_STATEMENT, YES);
	}
	PC_AppendCmd(PCD_GOTO);
	PC_AppendLong(incAddr);
	WriteContinues(incAddr);
	WriteBreaks();
	PC_WriteLong(pc_Address,gotoAddr);
}

//==========================================================================
//
// LeadingWhileUntil
//
//==========================================================================

static void LeadingWhileUntil(void)
{
	tokenType_t stmtToken;
	int topAddr;
	int outAddrPtr;

	MS_Message(MSG_DEBUG, "---- LeadingWhileUntil ----\n");
	stmtToken = tk_Token;
	topAddr = pc_Address;
	TK_NextTokenMustBe(TK_LPAREN, ERR_MISSING_LPAREN);
	TK_NextToken();
	EvalExpression();
	TK_TokenMustBe(TK_RPAREN, ERR_MISSING_RPAREN);
	PC_AppendCmd(stmtToken == TK_WHILE ? PCD_IFNOTGOTO : PCD_IFGOTO);
	outAddrPtr = pc_Address;
	PC_SkipLong();
	TK_NextToken();
	if(ProcessStatement(STMT_WHILEUNTIL) == NO)
	{
		ERR_Error(ERR_INVALID_STATEMENT, YES);
	}
	PC_AppendCmd(PCD_GOTO);
	PC_AppendLong(topAddr);

	PC_WriteLong(pc_Address, outAddrPtr);

	WriteContinues(topAddr);
	WriteBreaks();
}

//==========================================================================
//
// LeadingDo
//
//==========================================================================

static void LeadingDo(void)
{
	int topAddr;
	int exprAddr;
	tokenType_t stmtToken;

	MS_Message(MSG_DEBUG, "---- LeadingDo ----\n");
	topAddr = pc_Address;
	TK_NextToken();
	if(ProcessStatement(STMT_DO) == NO)
	{
		ERR_Error(ERR_INVALID_STATEMENT, YES, NULL);
	}
	if(tk_Token != TK_WHILE && tk_Token != TK_UNTIL)
	{
		ERR_Error(ERR_BAD_DO_STATEMENT, YES, NULL);
		TK_SkipPast(TK_SEMICOLON);
		return;
	}
	stmtToken = tk_Token;
	TK_NextTokenMustBe(TK_LPAREN, ERR_MISSING_LPAREN);
	exprAddr = pc_Address;
	TK_NextToken();
	EvalExpression();
	TK_TokenMustBe(TK_RPAREN, ERR_MISSING_RPAREN);
	TK_NextTokenMustBe(TK_SEMICOLON, ERR_MISSING_SEMICOLON);
	PC_AppendCmd(stmtToken == TK_WHILE ? PCD_IFGOTO : PCD_IFNOTGOTO);
	PC_AppendLong(topAddr);
	WriteContinues(exprAddr);
	WriteBreaks();
	TK_NextToken();
}

//==========================================================================
//
// LeadingSwitch
//
//==========================================================================

static void LeadingSwitch(void)
{
	int switcherAddrPtr;
	int outAddrPtr;
	caseInfo_t *cInfo;
	int defaultAddress;

	MS_Message(MSG_DEBUG, "---- LeadingSwitch ----\n");

	TK_NextTokenMustBe(TK_LPAREN, ERR_MISSING_LPAREN);
	TK_NextToken();
	EvalExpression();
	TK_TokenMustBe(TK_RPAREN, ERR_MISSING_RPAREN);

	PC_AppendCmd(PCD_GOTO);
	switcherAddrPtr = pc_Address;
	PC_SkipLong();

	TK_NextToken();
	if(ProcessStatement(STMT_SWITCH) == NO)
	{
		ERR_Error(ERR_INVALID_STATEMENT, YES, NULL);
	}

	PC_AppendCmd(PCD_GOTO);
	outAddrPtr = pc_Address;
	PC_SkipLong();

	PC_WriteLong(pc_Address, switcherAddrPtr);
	defaultAddress = 0;

	if(pc_HexenCase)
	{
		while((cInfo = GetCaseInfo()) != NULL)
		{
			if(cInfo->isDefault == YES)
			{
				defaultAddress = cInfo->address;
				continue;
			}
			PC_AppendCmd(PCD_CASEGOTO);
			PC_AppendLong(cInfo->value);
			PC_AppendLong(cInfo->address);
		}
	}
	else if(CaseIndex != 0)
	{
		caseInfo_t *maxCase = &CaseInfo[CaseIndex];
		caseInfo_t *minCase = maxCase;

		// [RH] Sort cases so that the VM can handle them with
		// a quick binary search.
		while((cInfo = GetCaseInfo()) != NULL)
		{
			minCase = cInfo;
		}
		qsort(minCase, maxCase - minCase, sizeof(caseInfo_t), CaseInfoCmp);
		if(minCase->isDefault == YES)
		{
			defaultAddress = minCase->address;
			minCase++;
		}
		if (minCase < maxCase)
		{
			PC_AppendCmd(PCD_CASEGOTOSORTED);
			if(pc_Address%4 != 0)
			{ // Align to a 4-byte boundary
				U_LONG pad = 0;
				PC_Append((void *)&pad, 4-(pc_Address%4));
			}
			PC_AppendLong(maxCase - minCase);
			for(; minCase < maxCase; ++minCase)
			{
				PC_AppendLong(minCase->value);
				PC_AppendLong(minCase->address);
			}
		}
	}
	PC_AppendCmd(PCD_DROP);

	if(defaultAddress != 0)
	{
		PC_AppendCmd(PCD_GOTO);
		PC_AppendLong(defaultAddress);
	}

	PC_WriteLong(pc_Address, outAddrPtr);

	WriteBreaks();
}

//==========================================================================
//
// LeadingCase
//
//==========================================================================

static void LeadingCase(void)
{
	MS_Message(MSG_DEBUG, "---- LeadingCase ----\n");
	TK_NextToken();
	PushCase(EvalConstExpression(), NO);
	TK_TokenMustBe(TK_COLON, ERR_MISSING_COLON);
	TK_NextToken();
}

//==========================================================================
//
// LeadingDefault
//
//==========================================================================

static void LeadingDefault(void)
{
	MS_Message(MSG_DEBUG, "---- LeadingDefault ----\n");
	TK_NextTokenMustBe(TK_COLON, ERR_MISSING_COLON);
	PushCase(0, YES);
	TK_NextToken();
}

//==========================================================================
//
// PushCase
//
//==========================================================================

static void PushCase(int value, boolean isDefault)
{
	if(CaseIndex == MAX_CASE)
	{
		ERR_Exit(ERR_CASE_OVERFLOW, YES);
	}
	CaseInfo[CaseIndex].level = StatementLevel;
	CaseInfo[CaseIndex].value = value;
	CaseInfo[CaseIndex].isDefault = isDefault;
	CaseInfo[CaseIndex].address = pc_Address;
	CaseIndex++;
}

//==========================================================================
//
// GetCaseInfo
//
//==========================================================================

static caseInfo_t *GetCaseInfo(void)
{
	if(CaseIndex == 0)
	{
		return NULL;
	}
	if(CaseInfo[CaseIndex-1].level > StatementLevel)
	{
		return &CaseInfo[--CaseIndex];
	}
	return NULL;
}

//==========================================================================
//
// CaseInfoCmp
//
//==========================================================================

static int CaseInfoCmp (const void *a, const void *b)
{
	const caseInfo_t *ca = (const caseInfo_t *)a;
	const caseInfo_t *cb = (const caseInfo_t *)b;

	// The default case always gets moved to the front.
	if(ca->isDefault)
	{
		return -1;
	}
	if(cb->isDefault)
	{
		return 1;
	}
	return ca->value - cb->value;
}

//==========================================================================
//
// DefaultInCurrent
//
//==========================================================================

static boolean DefaultInCurrent(void)
{
	int i;

	for(i = 0; i < CaseIndex; i++)
	{
		if(CaseInfo[i].isDefault == YES
			&& CaseInfo[i].level == StatementLevel)
		{
			return YES;
		}
	}
	return NO;
}

//==========================================================================
//
// LeadingBreak
//
//==========================================================================

static void LeadingBreak(void)
{
	MS_Message(MSG_DEBUG, "---- LeadingBreak ----\n");
	TK_NextTokenMustBe(TK_SEMICOLON, ERR_MISSING_SEMICOLON);
	PC_AppendCmd(PCD_GOTO);
	PushBreak();
	PC_SkipLong();
	TK_NextToken();
}

//==========================================================================
//
// PushBreak
//
//==========================================================================

static void PushBreak(void)
{
	if(BreakIndex == MAX_CASE)
	{
		ERR_Exit(ERR_BREAK_OVERFLOW, YES);
	}
	BreakInfo[BreakIndex].level = StatementLevel;
	BreakInfo[BreakIndex].addressPtr = pc_Address;
	BreakIndex++;
}

//==========================================================================
//
// WriteBreaks
//
//==========================================================================

static void WriteBreaks(void)
{
	while(BreakIndex && BreakInfo[BreakIndex-1].level > StatementLevel)
	{
		PC_WriteLong(pc_Address, BreakInfo[--BreakIndex].addressPtr);
	}
}

//==========================================================================
//
// BreakAncestor
//
// Returns YES if the current statement history contains a break root
// statement.
//
//==========================================================================

static boolean BreakAncestor(void)
{
	int i;

	for(i = 0; i < StatementIndex; i++)
	{
		if(IsBreakRoot[StatementHistory[i]])
		{
			return YES;
		}
	}
	return NO;
}

//==========================================================================
//
// LeadingContinue
//
//==========================================================================

static void LeadingContinue(void)
{
	MS_Message(MSG_DEBUG, "---- LeadingContinue ----\n");
	TK_NextTokenMustBe(TK_SEMICOLON, ERR_MISSING_SEMICOLON);
	PC_AppendCmd(PCD_GOTO);
	PushContinue();
	PC_SkipLong();
	TK_NextToken();
}

//==========================================================================
//
// PushContinue
//
//==========================================================================

static void PushContinue(void)
{
	if(ContinueIndex == MAX_CONTINUE)
	{
		ERR_Exit(ERR_CONTINUE_OVERFLOW, YES);
	}
	ContinueInfo[ContinueIndex].level = StatementLevel;
	ContinueInfo[ContinueIndex].addressPtr = pc_Address;
	ContinueIndex++;
}

//==========================================================================
//
// WriteContinues
//
//==========================================================================

static void WriteContinues(int address)
{
	if(ContinueIndex == 0)
	{
		return;
	}
	while(ContinueInfo[ContinueIndex-1].level > StatementLevel)
	{
		PC_WriteLong(address, ContinueInfo[--ContinueIndex].addressPtr);
	}
}

//==========================================================================
//
// ContinueAncestor
//
//==========================================================================

static boolean ContinueAncestor(void)
{
	int i;

	for(i = 0; i < StatementIndex; i++)
	{
		if(IsContinueRoot[StatementHistory[i]])
		{
			return YES;
		}
	}
	return NO;
}

static void LeadingIncDec(int token)
{
	symbolNode_t *sym;

	MS_Message(MSG_DEBUG, "---- LeadingIncDec ----\n");
	TK_NextTokenMustBe(TK_IDENTIFIER, ERR_INCDEC_OP_ON_NON_VAR);
	sym = DemandSymbol(tk_String);
	if(sym->type != SY_SCRIPTVAR && sym->type != SY_MAPVAR
		&& sym->type != SY_WORLDVAR && sym->type != SY_GLOBALVAR
		&& sym->type != SY_MAPARRAY && sym->type != SY_GLOBALARRAY
		&& sym->type != SY_WORLDARRAY)
	{
		ERR_Error(ERR_INCDEC_OP_ON_NON_VAR, YES);
		TK_SkipPast(TK_SEMICOLON);
		return;
	}
	TK_NextToken();
	if(sym->type == SY_MAPARRAY || sym->type == SY_WORLDARRAY
		|| sym->type == SY_GLOBALARRAY)
	{
		ParseArrayIndices(sym);
	}
	else if(tk_Token == TK_LBRACKET)
	{
		ERR_Error(ERR_NOT_AN_ARRAY, YES, sym->name);
		while(tk_Token == TK_LBRACKET)
		{
			TK_SkipPast(TK_RBRACKET);
		}
	}
	PC_AppendCmd(GetIncDecPCD(token, sym->type));
	PC_AppendShrink(sym->info.var.index);
	if(forSemicolonHack)
	{
		TK_TokenMustBe(TK_RPAREN, ERR_MISSING_RPAREN);
	}
	else
	{
		TK_TokenMustBe(TK_SEMICOLON, ERR_MISSING_SEMICOLON);
	}
	TK_NextToken();
}

//==========================================================================
//
// LeadingVarAssign
//
//==========================================================================

static void LeadingVarAssign(symbolNode_t *sym)
{
	boolean done;
	tokenType_t assignToken;

	MS_Message(MSG_DEBUG, "---- LeadingVarAssign ----\n");
	done = NO;
	do
	{
		TK_NextToken(); // Fetch assignment operator
		if(sym->type == SY_MAPARRAY || sym->type == SY_WORLDARRAY
			|| sym->type == SY_GLOBALARRAY)
		{
			ParseArrayIndices(sym);
		}
		else if(tk_Token == TK_LBRACKET)
		{
			ERR_Error(ERR_NOT_AN_ARRAY, YES, sym->name);
			while(tk_Token == TK_LBRACKET)
			{
				TK_SkipPast(TK_RBRACKET);
			}
		}
		if(tk_Token == TK_INC || tk_Token == TK_DEC)
		{ // Postfix increment or decrement
			PC_AppendCmd(GetIncDecPCD(tk_Token, sym->type));
			if (pc_NoShrink)
			{
				PC_AppendLong(sym->info.var.index);
			}
			else
			{
				PC_AppendByte(sym->info.var.index);
			}
			TK_NextToken();
		}
		else
		{ // Normal operator
			if(TK_Member(AssignOps) == NO)
			{
				ERR_Error(ERR_MISSING_ASSIGN_OP, YES);
				TK_SkipPast(TK_SEMICOLON);
				return;
			}
			assignToken = tk_Token;
			TK_NextToken();
			EvalExpression();
			PC_AppendCmd(GetAssignPCD(assignToken, sym->type));
			PC_AppendShrink(sym->info.var.index);
		}
		if(tk_Token == TK_COMMA)
		{
			TK_NextTokenMustBe(TK_IDENTIFIER, ERR_BAD_ASSIGNMENT);
			sym = DemandSymbol(tk_String);
			if(sym->type != SY_SCRIPTVAR && sym->type != SY_MAPVAR
				&& sym->type != SY_WORLDVAR && sym->type != SY_GLOBALVAR
				&& sym->type != SY_WORLDARRAY && sym->type != SY_GLOBALARRAY)
			{
				ERR_Error(ERR_BAD_ASSIGNMENT, YES);
				TK_SkipPast(TK_SEMICOLON);
				return;
			}
		}
		else
		{
			TK_TokenMustBe(TK_SEMICOLON, ERR_MISSING_SEMICOLON);
			TK_NextToken();
			done = YES;
		}
	} while(done == NO);
}

//==========================================================================
//
// GetAssignPCD
//
//==========================================================================

static pcd_t GetAssignPCD(tokenType_t token, symbolType_t symbol)
{
	size_t i, j;
	static tokenType_t tokenLookup[] =
	{
		TK_ASSIGN, TK_ADDASSIGN, TK_SUBASSIGN,
		TK_MULASSIGN, TK_DIVASSIGN, TK_MODASSIGN
	};
	static symbolType_t symbolLookup[] =
	{
		SY_SCRIPTVAR, SY_MAPVAR, SY_WORLDVAR, SY_GLOBALVAR, SY_MAPARRAY,
		SY_WORLDARRAY, SY_GLOBALARRAY
	};
	static pcd_t assignmentLookup[6][7] =
	{
		{ PCD_ASSIGNSCRIPTVAR, PCD_ASSIGNMAPVAR, PCD_ASSIGNWORLDVAR, PCD_ASSIGNGLOBALVAR, PCD_ASSIGNMAPARRAY, PCD_ASSIGNWORLDARRAY, PCD_ASSIGNGLOBALARRAY },
		{ PCD_ADDSCRIPTVAR, PCD_ADDMAPVAR, PCD_ADDWORLDVAR, PCD_ADDGLOBALVAR, PCD_ADDMAPARRAY, PCD_ADDWORLDARRAY, PCD_ADDGLOBALARRAY },
		{ PCD_SUBSCRIPTVAR, PCD_SUBMAPVAR, PCD_SUBWORLDVAR, PCD_SUBGLOBALVAR, PCD_SUBMAPARRAY, PCD_SUBWORLDARRAY, PCD_SUBGLOBALARRAY },
		{ PCD_MULSCRIPTVAR, PCD_MULMAPVAR, PCD_MULWORLDVAR, PCD_MULGLOBALVAR, PCD_MULMAPARRAY, PCD_MULWORLDARRAY, PCD_MULGLOBALARRAY },
		{ PCD_DIVSCRIPTVAR, PCD_DIVMAPVAR, PCD_DIVWORLDVAR, PCD_DIVGLOBALVAR, PCD_DIVMAPARRAY, PCD_DIVWORLDARRAY, PCD_DIVGLOBALARRAY },
		{ PCD_MODSCRIPTVAR, PCD_MODMAPVAR, PCD_MODWORLDVAR, PCD_MODGLOBALVAR, PCD_MODMAPARRAY, PCD_MODWORLDARRAY, PCD_MODGLOBALARRAY }
	};

	for(i = 0; i < ARRAY_SIZE(tokenLookup); ++i)
	{
		if(tokenLookup[i] == token)
		{
			for(j = 0; j < ARRAY_SIZE(symbolLookup); ++j)
			{
				if (symbolLookup[j] == symbol)
				{
					return assignmentLookup[i][j];
				}
			}
			break;
		}
	}
	return PCD_NOP;
}

//==========================================================================
//
// LeadingSuspend
//
//==========================================================================

static void LeadingSuspend(void)
{
	MS_Message(MSG_DEBUG, "---- LeadingSuspend ----\n");
	if(InsideFunction)
	{
		ERR_Error(ERR_SUSPEND_IN_FUNCTION, YES);
	}
	TK_NextTokenMustBe(TK_SEMICOLON, ERR_MISSING_SEMICOLON);
	PC_AppendCmd(PCD_SUSPEND);
	TK_NextToken();
}

//==========================================================================
//
// LeadingTerminate
//
//==========================================================================

static void LeadingTerminate(void)
{
	MS_Message(MSG_DEBUG, "---- LeadingTerminate ----\n");
	if(InsideFunction)
	{
		ERR_Error(ERR_TERMINATE_IN_FUNCTION, YES);
	}
	TK_NextTokenMustBe(TK_SEMICOLON, ERR_MISSING_SEMICOLON);
	PC_AppendCmd(PCD_TERMINATE);
	TK_NextToken();
}

//==========================================================================
//
// LeadingRestart
//
//==========================================================================

static void LeadingRestart(void)
{
	MS_Message(MSG_DEBUG, "---- LeadingRestart ----\n");
	if(InsideFunction)
	{
		ERR_Error(ERR_RESTART_IN_FUNCTION, YES);
	}
	TK_NextTokenMustBe(TK_SEMICOLON, ERR_MISSING_SEMICOLON);
	PC_AppendCmd(PCD_RESTART);
	TK_NextToken();
}

//==========================================================================
//
// LeadingReturn
//
//==========================================================================

static void LeadingReturn(void)
{
	MS_Message(MSG_DEBUG, "---- LeadingReturn ----\n");
	if(!InsideFunction)
	{
		ERR_Error(ERR_RETURN_OUTSIDE_FUNCTION, YES);
		while (TK_NextToken () != TK_SEMICOLON)
		{
			if (tk_Token == TK_EOF)
				break;
		}
	}
	else
	{
		TK_NextToken();
		if(tk_Token == TK_SEMICOLON)
		{
			if(InsideFunction->info.scriptFunc.hasReturnValue)
			{
				ERR_Error(ERR_MUST_RETURN_A_VALUE, YES);
			}
			PC_AppendCmd(PCD_RETURNVOID);
		}
		else
		{
			if(!InsideFunction->info.scriptFunc.hasReturnValue)
			{
				ERR_Error(ERR_MUST_NOT_RETURN_A_VALUE, YES);
			}
			EvalExpression();
			TK_TokenMustBe(TK_SEMICOLON, ERR_MISSING_SEMICOLON);
			PC_AppendCmd(PCD_RETURNVAL);
		}
		TK_NextToken();
	}
}

//==========================================================================
//
// EvalConstExpression
//
//==========================================================================

static int EvalConstExpression(void)
{
	pa_ConstExprIsString = NO;	// Used by PC_PutMapVariable
	ExprStackIndex = 0;
	ConstantExpression = YES;
	ExprLevA();
	if(ExprStackIndex != 1)
	{
		ERR_Error(ERR_BAD_CONST_EXPR, YES, NULL);
		ExprStack[0] = 0;
		ExprStackIndex = 1;
	}
	return PopExStk();
}

//==========================================================================
//
// EvalExpression
//
//==========================================================================

static void EvalExpression(void)
{
	ConstantExpression = NO;
	ExprLevA();
}

// Operator: ||
static void ExprLevA(void)
{
	ExprLevB();
	while(tk_Token == TK_ORLOGICAL)
	{
		TK_NextToken();
		ExprLevB();
		SendExprCommand(PCD_ORLOGICAL);
	}
}

// Operator: &&
static void ExprLevB(void)
{
	ExprLevC();
	while(tk_Token == TK_ANDLOGICAL)
	{
		TK_NextToken();
		ExprLevC();
		SendExprCommand(PCD_ANDLOGICAL);
	}
}

// Operator: |
static void ExprLevC(void)
{
	ExprLevD();
	while(tk_Token == TK_ORBITWISE)
	{
		TK_NextToken();
		ExprLevD();
		SendExprCommand(PCD_ORBITWISE);
	}
}

// Operator: ^
static void ExprLevD(void)
{
	ExprLevE();
	while(tk_Token == TK_EORBITWISE)
	{
		TK_NextToken();
		ExprLevE();
		SendExprCommand(PCD_EORBITWISE);
	}
}

// Operator: &
static void ExprLevE(void)
{
	ExprLevF();
	while(tk_Token == TK_ANDBITWISE)
	{
		TK_NextToken();
		ExprLevF();
		SendExprCommand(PCD_ANDBITWISE);
	}
}

// Operators: == !=
static void ExprLevF(void)
{
	tokenType_t token;

	ExprLevG();
	while(TK_Member(LevFOps))
	{
		token = tk_Token;
		TK_NextToken();
		ExprLevG();
		SendExprCommand(TokenToPCD(token));
	}
}

// Operators: < <= > >=
static void ExprLevG(void)
{
	tokenType_t token;

	ExprLevH();
	while(TK_Member(LevGOps))
	{
		token = tk_Token;
		TK_NextToken();
		ExprLevH();
		SendExprCommand(TokenToPCD(token));
	}
}

// Operators: << >>
static void ExprLevH(void)
{
	tokenType_t token;

	ExprLevI();
	while(TK_Member(LevHOps))
	{
		token = tk_Token;
		TK_NextToken();
		ExprLevI();
		SendExprCommand(TokenToPCD(token));
	}
}

// Operators: + -
static void ExprLevI(void)
{
	tokenType_t token;

	ExprLevJ();
	while(TK_Member(LevIOps))
	{
		token = tk_Token;
		TK_NextToken();
		ExprLevJ();
		SendExprCommand(TokenToPCD(token));
	}
}

// Operators: * / %
static void ExprLevJ(void)
{
	tokenType_t token;
	boolean unaryMinus;

	unaryMinus = FALSE;
	if(tk_Token == TK_MINUS)
	{
		unaryMinus = TRUE;
		TK_NextToken();
	}
	if(ConstantExpression == YES)
	{
		ConstExprFactor();
	}
	else
	{
		ExprFactor();
	}
	if(unaryMinus == TRUE)
	{
		SendExprCommand(PCD_UNARYMINUS);
	}
	while(TK_Member(LevJOps))
	{
		token = tk_Token;
		TK_NextToken();
		if(ConstantExpression == YES)
		{
			ConstExprFactor();
		}
		else
		{
			ExprFactor();
		}
		SendExprCommand(TokenToPCD(token));
	}
}

static void ExprLineSpecial(void)
{
	U_BYTE specialValue = tk_SpecialValue;
	int argCountMin = tk_SpecialArgCount & 0xffff;
	int argCountMax = tk_SpecialArgCount >> 16;

	// There are two ways to use a special in an expression:
	// 1. The special name by itself returns the special's number.
	// 2. The special followed by parameters actually executes the special.
	TK_NextToken();
	if(tk_Token != TK_LPAREN)
	{
		PC_AppendPushVal(tk_SpecialValue);
	}
	else
	{
		int argCount = 0;

		TK_NextToken();
		if(tk_Token != TK_RPAREN)
		{
			TK_Undo();
			do
			{
				TK_NextToken();
				EvalExpression();
				argCount++;
			} while(tk_Token == TK_COMMA);
		}
		if(argCount < argCountMin || argCount > argCountMax)
		{
			ERR_Error(ERR_BAD_LSPEC_ARG_COUNT, YES);
			return;
		}
		for(; argCount < 5; ++argCount)
		{
			PC_AppendPushVal(0);
		}
		TK_TokenMustBe(TK_RPAREN, ERR_MISSING_RPAREN);
		TK_NextToken();
		PC_AppendCmd(PCD_LSPEC5RESULT);
		if(pc_NoShrink)
		{
			PC_AppendLong(specialValue);
		}
		else
		{
			PC_AppendByte(specialValue);
		}
	}
}

static void ExprFactor(void)
{
	symbolNode_t *sym;
	tokenType_t opToken;

	switch(tk_Token)
	{
	case TK_STRING:
		if (ImportMode != IMPORT_Importing)
		{
			tk_Number = STR_Find(tk_String);
			PC_AppendPushVal(tk_Number);
			if (ImportMode == IMPORT_Exporting)
			{
				// The VM identifies strings by storing a library ID in the
				// high word of the string index. The library ID is not
				// known until the script actually gets loaded into the game,
				// so we need to use this p-code to tack the ID of the
				// currently running library to the index of the string that
				// just got pushed onto the stack.
				//
				// Simply assuming that a string is from the current library
				// is not good enough, because they can be passed around
				// between libraries and the map's behavior. Thus, we need
				// to know which object file a particular string belongs to.
				//
				// A specific example:
				// A map's behavior calls a function in a library and passes
				// a string:
				//
				//    LibFunc ("The library will do something with this string.");
				//
				// The library function needs to know that the string originated
				// outside the library. Similarly, if a library function returns
				// a string, the caller needs to know that the string did not
				// originate from the same object file.
				//
				// And that's why strings have library IDs tacked onto them.
				// The map's main behavior (i.e. an object that is not a library)
				// always uses library ID 0 to identify its strings, so its
				// strings don't need to be tagged.
				PC_AppendCmd(PCD_TAGSTRING);
			}
		}
		TK_NextToken();
		break;
	case TK_NUMBER:
		PC_AppendPushVal(tk_Number);
		TK_NextToken();
		break;
	case TK_LPAREN:
		TK_NextToken();
		ExprLevA();
		if(tk_Token != TK_RPAREN)
		{
			ERR_Error(ERR_BAD_EXPR, YES, NULL);
			TK_SkipPast(TK_RPAREN);
		}
		else
		{
			TK_NextToken();
		}
		break;
	case TK_NOT:
		TK_NextToken();
		ExprFactor();
		PC_AppendCmd(PCD_NEGATELOGICAL);
		break;
	case TK_INC:
	case TK_DEC:
		opToken = tk_Token;
		TK_NextTokenMustBe(TK_IDENTIFIER, ERR_INCDEC_OP_ON_NON_VAR);
		sym = DemandSymbol(tk_String);
		if(sym->type != SY_SCRIPTVAR && sym->type != SY_MAPVAR
			&& sym->type != SY_WORLDVAR && sym->type != SY_GLOBALVAR
			&& sym->type != SY_MAPARRAY && sym->type != SY_WORLDARRAY
			&& sym->type != SY_GLOBALARRAY)
		{
			ERR_Error(ERR_INCDEC_OP_ON_NON_VAR, YES);
		}
		else
		{
			TK_NextToken();
			if(sym->type == SY_MAPARRAY || sym->type == SY_WORLDARRAY
				|| sym->type == SY_GLOBALARRAY)
			{
				ParseArrayIndices(sym);
				PC_AppendCmd(PCD_DUP);
			}
			else if(tk_Token == TK_LBRACKET)
			{
				ERR_Error(ERR_NOT_AN_ARRAY, YES, sym->name);
				while(tk_Token == TK_LBRACKET)
				{
					TK_SkipPast(TK_RBRACKET);
				}
			}
			PC_AppendCmd(GetIncDecPCD(opToken, sym->type));
			PC_AppendShrink(sym->info.var.index);
			PC_AppendCmd(GetPushVarPCD(sym->type));
			PC_AppendShrink(sym->info.var.index);
		}
		break;
	case TK_IDENTIFIER:
		sym = SpeculateSymbol(tk_String, YES);
		switch(sym->type)
		{
			case SY_MAPARRAY:
			case SY_WORLDARRAY:
			case SY_GLOBALARRAY:
				TK_NextToken();
				ParseArrayIndices(sym);
				// fallthrough
			case SY_SCRIPTVAR:
			case SY_MAPVAR:
			case SY_WORLDVAR:
			case SY_GLOBALVAR:
				if(sym->type != SY_MAPARRAY && sym->type != SY_WORLDARRAY
					&& sym->type != SY_GLOBALARRAY)
				{
					TK_NextToken();
					if(tk_Token == TK_LBRACKET)
					{
						ERR_Error(ERR_NOT_AN_ARRAY, YES, sym->name);
						while(tk_Token == TK_LBRACKET)
						{
							TK_SkipPast(TK_RBRACKET);
						}
					}
				}
				if((tk_Token == TK_INC || tk_Token == TK_DEC)
					&& (sym->type == SY_MAPARRAY || sym->type == SY_WORLDARRAY
						|| sym->type == SY_GLOBALARRAY))
				{
					PC_AppendCmd(PCD_DUP);
				}
				PC_AppendCmd(GetPushVarPCD(sym->type));
				PC_AppendShrink(sym->info.var.index);
				if(tk_Token == TK_INC || tk_Token == TK_DEC)
				{
					if(sym->type == SY_MAPARRAY || sym->type == SY_WORLDARRAY
						|| sym->type == SY_GLOBALARRAY)
					{
						PC_AppendCmd(PCD_SWAP);
					}
					PC_AppendCmd(GetIncDecPCD(tk_Token, sym->type));
					PC_AppendShrink(sym->info.var.index);
					TK_NextToken();
				}
				break;
			case SY_INTERNFUNC:
				if(sym->info.internFunc.hasReturnValue == NO)
				{
					ERR_Error(ERR_EXPR_FUNC_NO_RET_VAL, YES);
				}
				ProcessInternFunc(sym);
				break;
			case SY_SCRIPTFUNC:
				if(sym->info.scriptFunc.hasReturnValue == NO)
				{
					ERR_Error(ERR_EXPR_FUNC_NO_RET_VAL, YES);
				}
				ProcessScriptFunc(sym, NO);
				break;
			default:
				ERR_Error(ERR_ILLEGAL_EXPR_IDENT, YES, tk_String);
				TK_NextToken();
				break;
		}
		break;
	case TK_LINESPECIAL:
		ExprLineSpecial();
		break;
	default:
		ERR_Error(ERR_BAD_EXPR, YES);
		TK_NextToken();
		break;
	}
}

static void ConstExprFactor(void)
{
	switch(tk_Token)
	{
	case TK_STRING:
		if (ImportMode != IMPORT_Importing)
		{
			int strnum = STR_Find(tk_String);
			if (ImportMode == IMPORT_Exporting)
			{
				pa_ConstExprIsString = YES;
			}
			PushExStk(strnum);
		}
		else
		{
			// Importing, so it doesn't matter
			PushExStk(0);
		}
		TK_NextToken();
		break;
	case TK_NUMBER:
		PushExStk(tk_Number);
		TK_NextToken();
		break;
	case TK_LPAREN:
		TK_NextToken();
		ExprLevA();
		if(tk_Token != TK_RPAREN)
		{
			ERR_Error(ERR_BAD_CONST_EXPR, YES);
			TK_SkipPast(TK_RPAREN);
		}
		else
		{
			TK_NextToken();
		}
		break;
	case TK_NOT:
		TK_NextToken();
		ConstExprFactor();
		SendExprCommand(PCD_NEGATELOGICAL);
		break;
	default:
		ERR_Error(ERR_BAD_CONST_EXPR, YES);
		PushExStk(0);
		while(tk_Token != TK_COMMA &&
			tk_Token != TK_SEMICOLON &&
			tk_Token != TK_RPAREN)
		{
			TK_NextToken();
		}
		break;
	}
}

//==========================================================================
//
// SendExprCommand
//
//==========================================================================

static void SendExprCommand(pcd_t pcd)
{
	int operand2;

	if(ConstantExpression == NO)
	{
		PC_AppendCmd(pcd);
		return;
	}
	switch(pcd)
	{
		case PCD_ADD:
			PushExStk(PopExStk()+PopExStk());
			break;
		case PCD_SUBTRACT:
			operand2 = PopExStk();
			PushExStk(PopExStk()-operand2);
			break;
		case PCD_MULTIPLY:
			PushExStk(PopExStk()*PopExStk());
			break;
		case PCD_DIVIDE:
			operand2 = PopExStk();
			PushExStk(PopExStk()/operand2);
			break;
		case PCD_MODULUS:
			operand2 = PopExStk();
			PushExStk(PopExStk()%operand2);
			break;
		case PCD_EQ:
			PushExStk(PopExStk() == PopExStk());
			break;
		case PCD_NE:
			PushExStk(PopExStk() != PopExStk());
			break;
		case PCD_LT:
			PushExStk(PopExStk() >= PopExStk());
			break;
		case PCD_GT:
			PushExStk(PopExStk() <= PopExStk());
			break;
		case PCD_LE:
			PushExStk(PopExStk() > PopExStk());
			break;
		case PCD_GE:
			PushExStk(PopExStk() < PopExStk());
			break;
		case PCD_ANDLOGICAL:
			PushExStk(PopExStk() && PopExStk());
			break;
		case PCD_ORLOGICAL:
			PushExStk(PopExStk() || PopExStk());
			break;
		case PCD_ANDBITWISE:
			PushExStk(PopExStk()&PopExStk());
			break;
		case PCD_ORBITWISE:
			PushExStk(PopExStk()|PopExStk());
			break;
		case PCD_EORBITWISE:
			PushExStk(PopExStk()^PopExStk());
			break;
		case PCD_NEGATELOGICAL:
			PushExStk(!PopExStk());
			break;
		case PCD_LSHIFT:
			operand2 = PopExStk();
			PushExStk(PopExStk()>>operand2);
			break;
		case PCD_RSHIFT:
			operand2 = PopExStk();
			PushExStk(PopExStk()<<operand2);
			break;
		case PCD_UNARYMINUS:
			PushExStk(-PopExStk());
			break;
		default:
			ERR_Exit(ERR_UNKNOWN_CONST_EXPR_PCD, YES);
			break;
	}
}

//==========================================================================
//
// PushExStk
//
//==========================================================================

static void PushExStk(int value)
{
	if(ExprStackIndex == EXPR_STACK_DEPTH)
	{
		ERR_Exit(ERR_EXPR_STACK_OVERFLOW, YES);
	}
	ExprStack[ExprStackIndex++] = value;
}

//==========================================================================
//
// PopExStk
//
//==========================================================================

static int PopExStk(void)
{
	if(ExprStackIndex < 1)
	{
		ERR_Error(ERR_EXPR_STACK_EMPTY, YES);
		return 0;
	}
	return ExprStack[--ExprStackIndex];
}

//==========================================================================
//
// TokenToPCD
//
//==========================================================================

static pcd_t TokenToPCD(tokenType_t token)
{
	int i;
	static struct
	{
		tokenType_t token;
		pcd_t pcd;
	}  operatorLookup[] =
	{
		{ TK_EQ,		PCD_EQ },
		{ TK_NE,		PCD_NE },
		{ TK_LT,		PCD_LT },
		{ TK_LE,		PCD_LE },
		{ TK_GT,		PCD_GT },
		{ TK_GE,		PCD_GE },
		{ TK_LSHIFT,	PCD_LSHIFT },
		{ TK_RSHIFT,	PCD_RSHIFT },
		{ TK_PLUS,		PCD_ADD },
		{ TK_MINUS,		PCD_SUBTRACT },
		{ TK_ASTERISK,	PCD_MULTIPLY },
		{ TK_SLASH,		PCD_DIVIDE },
		{ TK_PERCENT,	PCD_MODULUS },
		{ TK_NONE,		PCD_NOP }
	};

	for(i = 0; operatorLookup[i].token != TK_NONE; i++)
	{
		if(operatorLookup[i].token == token)
		{
			return operatorLookup[i].pcd;
		}
	}
	return PCD_NOP;
}

//==========================================================================
//
// GetPushVarPCD
//
//==========================================================================

static pcd_t GetPushVarPCD(symbolType_t symType)
{
	switch(symType)
	{
		case SY_SCRIPTVAR:
			return PCD_PUSHSCRIPTVAR;
		case SY_MAPVAR:
			return PCD_PUSHMAPVAR;
		case SY_WORLDVAR:
			return PCD_PUSHWORLDVAR;
		case SY_GLOBALVAR:
			return PCD_PUSHGLOBALVAR;
		case SY_MAPARRAY:
			return PCD_PUSHMAPARRAY;
		case SY_WORLDARRAY:
			return PCD_PUSHWORLDARRAY;
		case SY_GLOBALARRAY:
			return PCD_PUSHGLOBALARRAY;
		default:
			break;
	}
	return PCD_NOP;
}

//==========================================================================
//
// GetIncDecPCD
//
//==========================================================================

static pcd_t GetIncDecPCD(tokenType_t token, symbolType_t symbol)
{
	int i;
	static struct
	{
		tokenType_t token;
		symbolType_t symbol;
		pcd_t pcd;
	}  incDecLookup[] =
	{
		{ TK_INC, SY_SCRIPTVAR, PCD_INCSCRIPTVAR },
		{ TK_INC, SY_MAPVAR, PCD_INCMAPVAR },
		{ TK_INC, SY_WORLDVAR, PCD_INCWORLDVAR },
		{ TK_INC, SY_GLOBALVAR, PCD_INCGLOBALVAR },
		{ TK_INC, SY_MAPARRAY, PCD_INCMAPARRAY },
		{ TK_INC, SY_WORLDARRAY, PCD_INCWORLDARRAY },
		{ TK_INC, SY_GLOBALARRAY, PCD_INCGLOBALARRAY },

		{ TK_DEC, SY_SCRIPTVAR, PCD_DECSCRIPTVAR },
		{ TK_DEC, SY_MAPVAR, PCD_DECMAPVAR },
		{ TK_DEC, SY_WORLDVAR, PCD_DECWORLDVAR },
		{ TK_DEC, SY_GLOBALVAR, PCD_DECGLOBALVAR },
		{ TK_DEC, SY_MAPARRAY, PCD_DECMAPARRAY },
		{ TK_DEC, SY_WORLDARRAY, PCD_DECWORLDARRAY },
		{ TK_DEC, SY_GLOBALARRAY, PCD_DECGLOBALARRAY },

		{ TK_NONE, SY_DUMMY, PCD_NOP }
	};

	for(i = 0; incDecLookup[i].token != TK_NONE; i++)
	{
		if(incDecLookup[i].token == token
			&& incDecLookup[i].symbol == symbol)
		{
			return incDecLookup[i].pcd;
		}
	}
	return PCD_NOP;
}

//==========================================================================
//
// ParseArrayIndices
//
//==========================================================================

static void ParseArrayIndices(symbolNode_t *sym)
{
	boolean warned = NO;
	int i;

	TK_TokenMustBe(TK_LBRACKET, ERR_MISSING_LBRACKET);
	i = 0;
	while(tk_Token == TK_LBRACKET)
	{
		TK_NextToken();
		if((sym->type == SY_MAPARRAY && i == sym->info.array.ndim) ||
			(sym->type != SY_MAPARRAY && i > 0))
		{
			if (!warned)
			{
				warned = YES;
				ERR_Error(ERR_TOO_MANY_DIM_USED, YES,
					sym->name, sym->info.array.ndim);
			}
		}
		if(i > 0)
		{
			PC_AppendPushVal(sym->info.array.dimensions[i-1]);
			PC_AppendCmd(PCD_MULTIPLY);
		}
		EvalExpression();
		if(i > 0)
		{
			PC_AppendCmd(PCD_ADD);
		}
		i++;
		TK_TokenMustBe(TK_RBRACKET, ERR_MISSING_RBRACKET);
		TK_NextToken();
	}
}

static int *ProcessArrayLevel(int level, int *entry, int ndim,
	int dims[MAX_ARRAY_DIMS], int muls[MAX_ARRAY_DIMS], char *name)
{
	int i;

	for(i = 0; i < dims[level-1]; ++i)
	{
		if(tk_Token == TK_COMMA)
		{
			entry += muls[level-1];
			TK_NextToken();
		}
		else if(tk_Token == TK_RBRACE)
		{
			TK_NextToken();
			if(level > 1)
			{
				return entry + muls[level-2] - i;
			}
			else
			{
				return entry + (muls[0]*dims[0]) - i;
			}
		}
		else
		{
			if(level == ndim)
			{
				if(tk_Token == TK_LBRACE)
				{
					ERR_Error(ERR_TOO_MANY_DIM_USED, YES, name, ndim);
					SkipBraceBlock(0);
					TK_NextToken();
					entry++;
				}
				else
				{
					*entry++ = EvalConstExpression();
					ArrayHasStrings |= pa_ConstExprIsString;
				}
			}
			else
			{
				TK_TokenMustBe(TK_LBRACE, ERR_MISSING_LBRACE_ARR);
				TK_NextToken();
				entry = ProcessArrayLevel(level+1, entry, ndim, dims, muls, name);
			}
			if(i < dims[level-1]-1)
			{
				if(tk_Token != TK_RBRACE)
				{
					TK_TokenMustBe(TK_COMMA, ERR_MISSING_COMMA);
					TK_NextToken();
				}
			}
			else
			{
				if(tk_Token != TK_COMMA)
				{
					TK_TokenMustBe(TK_RBRACE, ERR_MISSING_RBRACE_ARR);
				}
				else
				{
					TK_NextToken();
				}
			}
		}
	}
	TK_TokenMustBe(TK_RBRACE, ERR_MISSING_RBRACE_ARR);
	TK_NextToken();
	return entry;
}

//==========================================================================
//
// InitializeArray
//
//==========================================================================

static void InitializeArray(symbolNode_t *sym, int dims[MAX_ARRAY_DIMS], int size)
{
	static int *entries = NULL;
	static int lastsize = -1;

	if(lastsize < size)
	{
		entries = MS_Realloc(entries, sizeof(int)*size, ERR_OUT_OF_MEMORY);
		lastsize = size;
	}
	memset(entries, 0, sizeof(int)*size);

	TK_NextTokenMustBe(TK_LBRACE, ERR_MISSING_LBRACE_ARR);
	TK_NextToken();
	ArrayHasStrings = NO;
	ProcessArrayLevel(1, entries, sym->info.array.ndim, dims,
		sym->info.array.dimensions, sym->name);
	if(ImportMode != IMPORT_Importing)
	{
		PC_InitArray(sym->info.array.index, entries, ArrayHasStrings);
	}
}

//==========================================================================
//
// DemandSymbol
//
//==========================================================================

static symbolNode_t *DemandSymbol(char *name)
{
	symbolNode_t *sym;

	if((sym = SY_Find(name)) == NULL)
	{
		ERR_Exit(ERR_UNKNOWN_IDENTIFIER, YES, name);
	}
	return sym;
}

//==========================================================================
//
// SpeculateSymbol
//
//==========================================================================

static symbolNode_t *SpeculateSymbol(char *name, boolean hasReturn)
{
	symbolNode_t *sym;

	sym = SY_Find(name);
	if(sym == NULL)
	{
		char name[MAX_IDENTIFIER_LENGTH];

		strcpy (name, tk_String);
		TK_NextToken();
		if(tk_Token == TK_LPAREN)
		{ // Looks like a function call
			sym = SpeculateFunction(name, hasReturn);
			TK_Undo();
		}
		else
		{
			ERR_Exit(ERR_UNKNOWN_IDENTIFIER, YES, name);
		}
	}
	return sym;
}

//==========================================================================
//
// SpeculateFunction
//
// Add a temporary symbol for a function that is used before it is defined.
//
//==========================================================================

static symbolNode_t *SpeculateFunction(const char *name, boolean hasReturn)
{
	symbolNode_t *sym;
	
	MS_Message(MSG_DEBUG, "---- SpeculateFunction %s ----\n", name);
	sym = SY_InsertGlobal(tk_String, SY_SCRIPTFUNC);
	sym->info.scriptFunc.predefined = YES;
	sym->info.scriptFunc.hasReturnValue = hasReturn;
	sym->info.scriptFunc.sourceLine = tk_Line;
	sym->info.scriptFunc.sourceName = tk_SourceName;
	sym->info.scriptFunc.argCount = -1;
	sym->info.scriptFunc.funcNumber = 0;
	return sym;
}

//==========================================================================
//
// UnspeculateFunction
//
// Fills in function calls that were made before this function was defined.
//
//==========================================================================

static void UnspeculateFunction(symbolNode_t *sym)
{
	prefunc_t *fillin;
	prefunc_t **prev;

	prev = &FillinFunctions;
	fillin = FillinFunctions;
	while(fillin != NULL)
	{
		prefunc_t *next = fillin->next;
		if(fillin->sym == sym)
		{
			if(fillin->argcount != sym->info.scriptFunc.argCount)
			{
				ERR_ErrorAt(fillin->source, fillin->line);
				ERR_Error(ERR_FUNC_ARGUMENT_COUNT, YES, sym->name,
					sym->info.scriptFunc.argCount,
					sym->info.scriptFunc.argCount == 1 ? "" : "s");
			}

			if(pc_NoShrink)
			{
				PC_WriteLong(sym->info.scriptFunc.funcNumber, fillin->address);
			}
			else
			{
				PC_WriteByte((U_BYTE)sym->info.scriptFunc.funcNumber, fillin->address);
			}
			if(FillinFunctionsLatest == &fillin->next)
			{
				FillinFunctionsLatest = prev;
			}
			free (fillin);
			*prev = next;
		}
		else
		{
			prev = &fillin->next;
		}
		fillin = next;
	}
}

//==========================================================================
//
// AddScriptFuncRef
//
//==========================================================================

static void AddScriptFuncRef(symbolNode_t *sym, int address, int argcount)
{
	prefunc_t *fillin = MS_Alloc(sizeof(*fillin), ERR_OUT_OF_MEMORY);
	fillin->next = NULL;
	fillin->sym = sym;
	fillin->address = address;
	fillin->argcount = argcount;
	fillin->line = tk_Line;
	fillin->source = tk_SourceName;
	*FillinFunctionsLatest = fillin;
	FillinFunctionsLatest = &fillin->next;
}

//==========================================================================
//
// Check for undefined functions
//
//==========================================================================

static void CheckForUndefinedFunctions(void)
{
	prefunc_t *fillin = FillinFunctions;

	while(fillin != NULL)
	{
		ERR_ErrorAt(fillin->source, fillin->line);
		ERR_Error(ERR_UNDEFINED_FUNC, YES, fillin->sym->name);
		fillin = fillin->next;
	}
}

//==========================================================================
//
// SkipBraceBlock
//
// If depth is 0, it scans for the first { and then starts going from there.
// At exit, the terminating } is left as the current token.
//
//==========================================================================

void SkipBraceBlock(int depth)
{
	if (depth == 0)
	{
		// Find first {
		while(tk_Token != TK_LBRACE)
		{
			if(tk_Token == TK_EOF)
			{
				ERR_Exit(ERR_EOF, NO);
			}
			TK_NextToken();
		}
		depth = 1;
	}
	// Match it with a }
	do
	{
		TK_NextToken();
		if(tk_Token == TK_EOF)
		{
			ERR_Exit(ERR_EOF, NO);
		}
		else if (tk_Token == TK_LBRACE)
		{
			depth++;
		}
		else if (tk_Token == TK_RBRACE)
		{
			depth--;
		}
	} while (depth > 0);
}
