thug/Code/Gel/Scripting/parse.cpp
2016-02-14 08:39:12 +11:00

3416 lines
104 KiB
C++

///////////////////////////////////////////////////////////////////////////////////////
//
// parse.cpp KSH 23 Oct 2001
//
// Functions for parsing qb files.
//
///////////////////////////////////////////////////////////////////////////////////////
#include <gel/scripting/parse.h>
#include <gel/scripting/tokens.h>
#include <gel/scripting/component.h>
#include <gel/scripting/struct.h>
#include <gel/scripting/array.h>
#include <gel/scripting/symboltable.h>
#include <gel/scripting/string.h>
#include <gel/scripting/vecpair.h>
#include <gel/scripting/checksum.h>
#include <gel/scripting/eval.h>
#include <gel/scripting/utils.h>
#include <gel/scripting/scriptcache.h>
#include <gel/scripting/script.h>
#include <core/math/math.h> // For Mth::Rnd and Mth::Rnd2
#include <core/crc.h> // For Crc::GenerateCRCFromString
#include <core/compress.h>
#ifdef __PLAT_NGC__
#include <sys/ngc/p_aram.h>
#include <sys/ngc/p_dma.h>
#endif // __PLAT_NGC__
DefinePoolableClass(Script::CStoredRandom);
namespace Script
{
class CScript;
// Declaring this seperately rather than including script.h to avoid a cyclic dependency.
// The full class declaration of CScript is not required.
extern CScript *GetCurrentScript();
static uint8 *sCalculateRandomRange(uint8 *p_token, CComponent *p_comp, bool useRnd2);
static uint8 *sSkipType(uint8 *p_token);
static bool sIsEndOfLine(const uint8 *p_token);
static uint8 *sInitArrayFromQB(CArray *p_dest, uint8 *p_token, CStruct *p_args=NULL);
static uint8 *sAddComponentFromQB(CStruct *p_dest, uint32 nameChecksum, uint8 *p_token, CStruct *p_args=NULL);
static uint8 *sAddComponentsWithinCurlyBraces(CStruct *p_dest, uint8 *p_token, CStruct *p_args=NULL);
static CSymbolTableEntry *sCreateScriptSymbol(uint32 nameChecksum, uint32 contentsChecksum, const uint8 *p_data, uint32 size, const char *p_fileName);
static uint8 *sCreateSymbolOfTheFormNameEqualsValue(uint8 *p_token, const char *p_fileName, EBoolAssertIfDuplicateSymbols assertIfDuplicateSymbols);
static CStoredRandom *sFindStoredRandom(const uint8 *p_token, EScriptToken type, int numItems);
static CStoredRandom *sCreateNewStoredRandom();
static CStoredRandom *sp_first_stored_random=NULL;
static CStoredRandom *sp_last_stored_random=NULL;
static int s_num_stored_randoms=0;
static uint32 s_qb_being_parsed=0;
// The SkipToken function is in skiptoken.cpp, so that it can also be included in PC code,
// such as qcomp.
#include <gel/scripting/skiptoken.cpp>
#ifdef __NOPT_ASSERT__
void CheckForPossibleInfiniteLoops(uint32 scriptName, uint8 *p_token, const char *p_fileName)
{
#define MAX_NESTED_LOOPS 100
bool loop_is_ok[MAX_NESTED_LOOPS];
int loop_index=0;
for (int i=0; i<MAX_NESTED_LOOPS; ++i)
{
loop_is_ok[i]=false;
}
Script::CArray *p_ok_scripts=Script::GetArray(CRCD(0x8b860fef,"DoNotAssertForInfiniteLoopsInTheseScripts"));
Script::CArray *p_blocking_functions=Script::GetArray(CRCD(0x215d13b9,"BlockingFunctions"));
while (*p_token!=ESCRIPTTOKEN_KEYWORD_ENDSCRIPT)
{
switch (*p_token)
{
case ESCRIPTTOKEN_KEYWORD_BEGIN:
loop_is_ok[loop_index++]=false; // It's guilty until proven innocent!
Dbg_MsgAssert(loop_index<=100,("Too many nested loops"));
p_token=SkipToken(p_token);
break;
case ESCRIPTTOKEN_KEYWORD_BREAK:
{
Dbg_MsgAssert(loop_index,("Zero loop_index"));
int i=loop_index-1;
while (i>=0)
{
loop_is_ok[i]=true;
--i;
}
p_token=SkipToken(p_token);
break;
}
case ESCRIPTTOKEN_KEYWORD_REPEAT:
p_token=SkipToken(p_token);
Dbg_MsgAssert(loop_index,("Zero loop_index"));
if (*p_token==ESCRIPTTOKEN_ENDOFLINE || *p_token==ESCRIPTTOKEN_ENDOFLINENUMBER)
{
if (!loop_is_ok[loop_index-1])
{
bool allow=false;
if (p_ok_scripts)
{
for (uint32 i=0; i<p_ok_scripts->GetSize(); ++i)
{
if (p_ok_scripts->GetChecksum(i)==scriptName)
{
allow=true;
}
}
}
if (allow)
{
}
else
{
Dbg_MsgAssert(0, ("Warning! Possible infinite loop: Line %d of %s\n",Script::GetLineNumber(p_token),p_fileName));
}
}
}
else
{
// If repeat is not followed by an end-of-line then it is probably followed by some
// count value, so it is not an infinite loop.
}
--loop_index;
break;
case ESCRIPTTOKEN_NAME:
{
++p_token;
uint32 name=Read4Bytes(p_token).mChecksum;
p_token+=4;
if (p_blocking_functions)
{
uint32 *p_function_names=p_blocking_functions->GetArrayPointer();
uint32 size=p_blocking_functions->GetSize();
for (uint32 i=0; i<size; ++i)
{
if (name == *p_function_names++)
{
if (loop_index)
{
int i=loop_index-1;
while (i>=0)
{
loop_is_ok[i]=true;
--i;
}
}
break;
}
}
}
break;
}
default:
p_token=SkipToken(p_token);
break;
}
}
}
#endif
// Used for evaluating the ESCRIPTTOKEN_KEYWORD_RANDOM_RANGE operator.
static uint8 *sCalculateRandomRange(uint8 *p_token, CComponent *p_comp, bool useRnd2)
{
// RandomRange token must be followed by a pair.
Dbg_MsgAssert(*p_token==ESCRIPTTOKEN_PAIR,("RandomRange operator must be followed by a pair of values in parentheses, File %s, line %d",GetSourceFile(p_token),GetLineNumber(p_token)));
++p_token;
// Get the range values, which are floats cos it's a pair.
float x=Read4Bytes(p_token).mFloat;
p_token+=4;
float y=Read4Bytes(p_token).mFloat;
p_token+=4;
// Get the integer values.
// Offset the floats up or down by 0.01 to ensure they round down to the expected integer values.
int a, b;
a=(int)( (x<0) ? x-0.1f:x+0.1f);
b=(int)( (y<0) ? y-0.1f:y+0.1f);
bool they_are_integers=true;
if (fabs(x-a) > 0.00001f || fabs(y-b) > 0.00001f)
{
they_are_integers=false;
}
if (they_are_integers)
{
// Make sure that a<=b
if (a>b)
{
// Swap them.
int temp=a;
a=b;
b=temp;
}
// Get val, which is between a and b inclusive.
// This assert is to make sure the calculations in Rnd don't overflow.
Dbg_MsgAssert(b-a+1<=32767,("File %s, line %d: RandomRange limits are too far apart, max is 32767",GetSourceFile(p_token),GetLineNumber(p_token)));
int val;
if (useRnd2)
{
val=a+Mth::Rnd2(b-a+1);
}
else
{
val=a+Mth::Rnd(b-a+1);
}
// Make totally sure the value is OK, cos could lead to hard-to-track-down bugs otherwise.
// Dbg_MsgAssert(val>=a && val<=b,("File %s, line %d: Internal error in RandomRange (%f %f %f), fire Ken.",GetSourceFile(p_token),GetLineNumber(p_token),x,y,val));
p_comp->mType=ESYMBOLTYPE_INTEGER;
p_comp->mIntegerValue=val;
}
else
{
// Make sure that x<=y
if (x>y)
{
// Swap them.
float temp=x;
x=y;
y=temp;
}
// Get val, which is between x and y inclusive.
float val;
// Don't make FLOAT_RES bigger than 32766 because the calculations in Rnd will overflow otherwise.
#define FLOAT_RES 32760.0f
if (useRnd2)
{
val=x+((float)Mth::Rnd2((int)(FLOAT_RES+1.0f))*(y-x))/FLOAT_RES;
}
else
{
val=x+((float)Mth::Rnd((int)(FLOAT_RES+1.0f))*(y-x))/FLOAT_RES;
}
// Make totally sure the value is OK, cos could lead to hard-to-track-down bugs otherwise.
// Dbg_MsgAssert(val>=x && val<=y,("File %s, line %d: Internal error in RandomRange (%f %f %f), fire Ken.",GetSourceFile(p_token),GetLineNumber(p_token),x,y,val));
p_comp->mType=ESYMBOLTYPE_FLOAT;
p_comp->mFloatValue=val;
}
return p_token;
}
// Skips over a data type token. Eg, if p_token points to a structure, it will
// skip over the whole structure.
static uint8 *sSkipType(uint8 *p_token)
{
switch (*p_token)
{
case ESCRIPTTOKEN_NAME:
case ESCRIPTTOKEN_INTEGER:
case ESCRIPTTOKEN_HEXINTEGER:
case ESCRIPTTOKEN_FLOAT:
p_token+=1+4;
break;
case ESCRIPTTOKEN_PAIR:
p_token+=1+2*4;
break;
case ESCRIPTTOKEN_VECTOR:
p_token+=1+3*4;
break;
case ESCRIPTTOKEN_STRING:
case ESCRIPTTOKEN_LOCALSTRING:
p_token+=1+4+Read4Bytes(p_token+1).mUInt;
break;
case ESCRIPTTOKEN_STARTSTRUCT:
{
int StructCount=1;
while (StructCount)
{
p_token=SkipToken(p_token);
if (*p_token==ESCRIPTTOKEN_STARTSTRUCT)
{
++StructCount;
}
else if (*p_token==ESCRIPTTOKEN_ENDSTRUCT)
{
--StructCount;
}
}
++p_token;
break;
}
case ESCRIPTTOKEN_STARTARRAY:
{
int ArrayCount=1;
while (ArrayCount)
{
p_token=SkipToken(p_token);
if (*p_token==ESCRIPTTOKEN_STARTARRAY)
{
++ArrayCount;
}
else if (*p_token==ESCRIPTTOKEN_ENDARRAY)
{
--ArrayCount;
}
}
++p_token;
break;
}
default:
Dbg_MsgAssert(0,("Unrecognized type"));
break;
}
return p_token;
}
// Returns true if pToken points to and end-of-line, end-of-line-number or end-of-file token.
static bool sIsEndOfLine(const uint8 *p_token)
{
Dbg_MsgAssert(p_token,("NULL p_token"));
return (*p_token==ESCRIPTTOKEN_ENDOFLINE || *p_token==ESCRIPTTOKEN_ENDOFLINENUMBER || *p_token==ESCRIPTTOKEN_ENDOFFILE);
}
// Given that p_token points to a ESCRIPTTOKEN_STARTARRAY token, this will parse the
// array tokens adding the elements to the CArray.
// Gives error messages if all the elements are not of the same type.
static uint8 *sInitArrayFromQB(CArray *p_dest, uint8 *p_token, CStruct *p_args)
{
Dbg_MsgAssert(p_dest,("NULL p_dest"));
Dbg_MsgAssert(p_token,("NULL p_token"));
Dbg_MsgAssert(*p_token==ESCRIPTTOKEN_STARTARRAY,("p_token does not point to an array"));
// Remember the start, since we're going to do a first pass through to determine the array type and size.
uint8 *p_start=p_token;
// Skip over the startarray token.
++p_token;
// Execute any random operators.
p_token=DoAnyRandomsOrJumps(p_token);
ESymbolType type=ESYMBOLTYPE_NONE;
uint32 size=0;
while (*p_token!=ESCRIPTTOKEN_ENDARRAY)
{
// Determine type.
switch (*p_token)
{
case ESCRIPTTOKEN_ENDOFLINE:
case ESCRIPTTOKEN_ENDOFLINENUMBER:
case ESCRIPTTOKEN_COMMA:
break;
case ESCRIPTTOKEN_NAME:
type=ESYMBOLTYPE_NAME;
break;
case ESCRIPTTOKEN_INTEGER:
case ESCRIPTTOKEN_HEXINTEGER:
// Integers don't override floats.
if (type!=ESYMBOLTYPE_FLOAT)
{
type=ESYMBOLTYPE_INTEGER;
}
break;
case ESCRIPTTOKEN_FLOAT:
type=ESYMBOLTYPE_FLOAT;
break;
case ESCRIPTTOKEN_PAIR:
type=ESYMBOLTYPE_PAIR;
break;
case ESCRIPTTOKEN_VECTOR:
type=ESYMBOLTYPE_VECTOR;
break;
case ESCRIPTTOKEN_STRING:
type=ESYMBOLTYPE_STRING;
break;
case ESCRIPTTOKEN_LOCALSTRING:
type=ESYMBOLTYPE_LOCALSTRING;
break;
case ESCRIPTTOKEN_STARTSTRUCT:
type=ESYMBOLTYPE_STRUCTURE;
break;
case ESCRIPTTOKEN_STARTARRAY:
type=ESYMBOLTYPE_ARRAY;
break;
default:
Dbg_MsgAssert(0,("Unrecognized data type in array, File %s, line %d\n",GetSourceFile(p_token),GetLineNumber(p_token)));
break;
}
// Update the size and advance p_token.
switch (*p_token)
{
case ESCRIPTTOKEN_ENDOFLINE:
case ESCRIPTTOKEN_ENDOFLINENUMBER:
case ESCRIPTTOKEN_COMMA:
p_token=SkipToken(p_token);
break;
case ESCRIPTTOKEN_NAME:
case ESCRIPTTOKEN_INTEGER:
case ESCRIPTTOKEN_HEXINTEGER:
case ESCRIPTTOKEN_FLOAT:
case ESCRIPTTOKEN_PAIR:
case ESCRIPTTOKEN_VECTOR:
case ESCRIPTTOKEN_STRING:
case ESCRIPTTOKEN_LOCALSTRING:
case ESCRIPTTOKEN_STARTSTRUCT:
case ESCRIPTTOKEN_STARTARRAY:
++size;
p_token=sSkipType(p_token);
break;
default:
Dbg_MsgAssert(0,("Unrecognized data type in array, File %s, line %d\n",GetSourceFile(p_token),GetLineNumber(p_token)));
break;
}
// Execute any random operators. This has to be done each time p_token is advanced.
p_token=DoAnyRandomsOrJumps(p_token);
}
if (type==ESYMBOLTYPE_NONE)
{
// Skip over the ESCRIPTTOKEN_ENDARRAY
++p_token;
// Finished, empty array.
return p_token;
}
// Rewind.
p_token=p_start;
// Now that the array size and type are known, set up the CArray and fill it in.
// Make sure we're using the script heap, because the CArray is not hard-wired to
// allocate it's buffer off it.
Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().ScriptHeap());
p_dest->SetSizeAndType(size,type);
Mem::Manager::sHandle().PopContext();
// Just to be totally sure, cos we're about to write into it ...
Dbg_MsgAssert(p_dest->GetArrayPointer(),("NULL array pinter ???"));
#ifdef __NOPT_ASSERT__
int size_check=size;
#endif
switch (type)
{
case ESYMBOLTYPE_INTEGER:
{
int *p_int=(int*)p_dest->GetArrayPointer();
// Skip over the ESCRIPTTOKEN_STARTARRAY
++p_token;
// Execute any random operators. This has to be done each time p_token is advanced.
p_token=DoAnyRandomsOrJumps(p_token);
while (*p_token!=ESCRIPTTOKEN_ENDARRAY)
{
switch (*p_token++)
{
case ESCRIPTTOKEN_ENDOFLINE:
case ESCRIPTTOKEN_COMMA:
break;
case ESCRIPTTOKEN_ENDOFLINENUMBER:
p_token+=4;
break;
case ESCRIPTTOKEN_INTEGER:
case ESCRIPTTOKEN_HEXINTEGER:
#ifdef __NOPT_ASSERT__
Dbg_MsgAssert(size_check,("Array size mismatch"));
--size_check;
#endif
*p_int++=Read4Bytes(p_token).mInt;
p_token+=4;
break;
default:
Dbg_MsgAssert(0,("Array elements must be of the same type, File %s, line %d\n",GetSourceFile(p_token-1),GetLineNumber(p_token-1)));
break;
}
// Execute any random operators. This has to be done each time p_token is advanced.
p_token=DoAnyRandomsOrJumps(p_token);
}
#ifdef __NOPT_ASSERT__
Dbg_MsgAssert(size_check==0,("Array size mismatch"));
#endif
// Skip over the ESCRIPTTOKEN_ENDARRAY
++p_token;
break;
}
case ESYMBOLTYPE_FLOAT:
{
float *p_float=(float*)p_dest->GetArrayPointer();
// Skip over the ESCRIPTTOKEN_STARTARRAY
++p_token;
p_token=DoAnyRandomsOrJumps(p_token);
while (*p_token!=ESCRIPTTOKEN_ENDARRAY)
{
switch (*p_token++)
{
case ESCRIPTTOKEN_ENDOFLINE:
case ESCRIPTTOKEN_COMMA:
break;
case ESCRIPTTOKEN_ENDOFLINENUMBER:
p_token+=4;
break;
case ESCRIPTTOKEN_INTEGER:
case ESCRIPTTOKEN_HEXINTEGER:
#ifdef __NOPT_ASSERT__
Dbg_MsgAssert(size_check,("Array size mismatch"));
--size_check;
#endif
*p_float++=Read4Bytes(p_token).mInt;
p_token+=4;
break;
case ESCRIPTTOKEN_FLOAT:
#ifdef __NOPT_ASSERT__
Dbg_MsgAssert(size_check,("Array size mismatch"));
--size_check;
#endif
*p_float++=Read4Bytes(p_token).mFloat;
p_token+=4;
break;
default:
Dbg_MsgAssert(0,("Array elements must be of the same type, File %s, line %d\n",GetSourceFile(p_token-1),GetLineNumber(p_token-1)));
break;
}
// Execute any random operators. This has to be done each time p_token is advanced.
p_token=DoAnyRandomsOrJumps(p_token);
}
#ifdef __NOPT_ASSERT__
Dbg_MsgAssert(size_check==0,("Array size mismatch"));
#endif
// Skip over the ESCRIPTTOKEN_ENDARRAY
++p_token;
break;
}
case ESYMBOLTYPE_NAME:
{
uint32 *p_checksum=(uint32*)p_dest->GetArrayPointer();
// Skip over the ESCRIPTTOKEN_STARTARRAY
++p_token;
p_token=DoAnyRandomsOrJumps(p_token);
while (*p_token!=ESCRIPTTOKEN_ENDARRAY)
{
switch (*p_token++)
{
case ESCRIPTTOKEN_ENDOFLINE:
case ESCRIPTTOKEN_COMMA:
break;
case ESCRIPTTOKEN_ENDOFLINENUMBER:
p_token+=4;
break;
case ESCRIPTTOKEN_NAME:
#ifdef __NOPT_ASSERT__
Dbg_MsgAssert(size_check,("Array size mismatch"));
--size_check;
#endif
*p_checksum++=Read4Bytes(p_token).mChecksum;
p_token+=4;
break;
default:
Dbg_MsgAssert(0,("Array elements must be of the same type, File %s, line %d\n",GetSourceFile(p_token-1),GetLineNumber(p_token-1)));
break;
}
// Execute any random operators. This has to be done each time p_token is advanced.
p_token=DoAnyRandomsOrJumps(p_token);
}
#ifdef __NOPT_ASSERT__
Dbg_MsgAssert(size_check==0,("Array size mismatch"));
#endif
// Skip over the ESCRIPTTOKEN_ENDARRAY
++p_token;
break;
}
case ESYMBOLTYPE_STRUCTURE:
{
CStruct **pp_structures=(CStruct**)p_dest->GetArrayPointer();
// For finding out which node in Chad's qn is causing a syntax error
//int index=0; // REMOVE!
// Skip over the ESCRIPTTOKEN_STARTARRAY
++p_token;
p_token=DoAnyRandomsOrJumps(p_token);
while (*p_token!=ESCRIPTTOKEN_ENDARRAY)
{
switch (*p_token)
{
case ESCRIPTTOKEN_ENDOFLINE:
case ESCRIPTTOKEN_COMMA:
++p_token;
break;
case ESCRIPTTOKEN_ENDOFLINENUMBER:
p_token+=5;
break;
case ESCRIPTTOKEN_STARTSTRUCT:
{
#ifdef __NOPT_ASSERT__
Dbg_MsgAssert(size_check,("Array size mismatch"));
--size_check;
#endif
// Each structure is individually allocated.
CStruct *p_struct=new CStruct;
p_token=sAddComponentsWithinCurlyBraces(p_struct,p_token,p_args);
*pp_structures++=p_struct;
//printf("Created array structure %d\n",index++); // REMOVE!
break;
}
default:
Dbg_MsgAssert(0,("Array elements must be of the same type, File %s, line %d\n",GetSourceFile(p_token),GetLineNumber(p_token)));
break;
}
// Execute any random operators. This has to be done each time p_token is advanced.
p_token=DoAnyRandomsOrJumps(p_token);
}
#ifdef __NOPT_ASSERT__
Dbg_MsgAssert(size_check==0,("Array size mismatch"));
#endif
// Skip over the ESCRIPTTOKEN_ENDARRAY
++p_token;
break;
}
case ESYMBOLTYPE_ARRAY:
{
CArray **pp_arrays=(CArray**)p_dest->GetArrayPointer();
// Skip over the ESCRIPTTOKEN_STARTARRAY
++p_token;
p_token=DoAnyRandomsOrJumps(p_token);
while (*p_token!=ESCRIPTTOKEN_ENDARRAY)
{
switch (*p_token++)
{
case ESCRIPTTOKEN_ENDOFLINE:
case ESCRIPTTOKEN_COMMA:
break;
case ESCRIPTTOKEN_ENDOFLINENUMBER:
p_token+=4;
break;
case ESCRIPTTOKEN_STARTARRAY:
{
#ifdef __NOPT_ASSERT__
Dbg_MsgAssert(size_check,("Array size mismatch"));
--size_check;
#endif
// Each array is individually allocated.
CArray *p_array=new CArray;
// The -1 is to rewind p_token back to pointing to ESCRIPTTOKEN_STARTARRAY,
// which sInitArrayFromQB requires.
p_token=sInitArrayFromQB(p_array,p_token-1,p_args);
*pp_arrays++=p_array;
break;
}
default:
Dbg_MsgAssert(0,("Array elements must be of the same type, File %s, line %d\n",GetSourceFile(p_token-1),GetLineNumber(p_token-1)));
break;
}
// Execute any random operators. This has to be done each time p_token is advanced.
p_token=DoAnyRandomsOrJumps(p_token);
}
#ifdef __NOPT_ASSERT__
Dbg_MsgAssert(size_check==0,("Array size mismatch"));
#endif
// Skip over the ESCRIPTTOKEN_ENDARRAY
++p_token;
break;
}
case ESYMBOLTYPE_STRING:
{
char **pp_strings=(char**)p_dest->GetArrayPointer();
// Skip over the ESCRIPTTOKEN_STARTARRAY
++p_token;
p_token=DoAnyRandomsOrJumps(p_token);
while (*p_token!=ESCRIPTTOKEN_ENDARRAY)
{
switch (*p_token++)
{
case ESCRIPTTOKEN_ENDOFLINE:
case ESCRIPTTOKEN_COMMA:
break;
case ESCRIPTTOKEN_ENDOFLINENUMBER:
p_token+=4;
break;
case ESCRIPTTOKEN_STRING:
{
#ifdef __NOPT_ASSERT__
Dbg_MsgAssert(size_check,("Array size mismatch"));
--size_check;
#endif
int string_length=Read4Bytes(p_token).mInt;
Dbg_MsgAssert(string_length,("Zero string_length?"));
p_token+=4;
*pp_strings++=CreateString((const char *)p_token);
p_token+=string_length;
break;
}
default:
Dbg_MsgAssert(0,("Array elements must be of the same type, File %s, line %d\n",GetSourceFile(p_token-1),GetLineNumber(p_token-1)));
break;
}
// Execute any random operators. This has to be done each time p_token is advanced.
p_token=DoAnyRandomsOrJumps(p_token);
}
#ifdef __NOPT_ASSERT__
Dbg_MsgAssert(size_check==0,("Array size mismatch"));
#endif
// Skip over the ESCRIPTTOKEN_ENDARRAY
++p_token;
break;
}
case ESYMBOLTYPE_LOCALSTRING:
{
char **pp_strings=(char**)p_dest->GetArrayPointer();
// Skip over the ESCRIPTTOKEN_STARTARRAY
++p_token;
p_token=DoAnyRandomsOrJumps(p_token);
while (*p_token!=ESCRIPTTOKEN_ENDARRAY)
{
switch (*p_token++)
{
case ESCRIPTTOKEN_ENDOFLINE:
case ESCRIPTTOKEN_COMMA:
break;
case ESCRIPTTOKEN_ENDOFLINENUMBER:
p_token+=4;
break;
case ESCRIPTTOKEN_LOCALSTRING:
{
#ifdef __NOPT_ASSERT__
Dbg_MsgAssert(size_check,("Array size mismatch"));
--size_check;
#endif
int string_length=Read4Bytes(p_token).mInt;
Dbg_MsgAssert(string_length,("Zero string_length?"));
p_token+=4;
*pp_strings++=CreateString((const char *)p_token);
p_token+=string_length;
break;
}
default:
Dbg_MsgAssert(0,("Array elements must be of the same type, File %s, line %d\n",GetSourceFile(p_token-1),GetLineNumber(p_token-1)));
break;
}
// Execute any random operators. This has to be done each time p_token is advanced.
p_token=DoAnyRandomsOrJumps(p_token);
}
#ifdef __NOPT_ASSERT__
Dbg_MsgAssert(size_check==0,("Array size mismatch"));
#endif
// Skip over the ESCRIPTTOKEN_ENDARRAY
++p_token;
break;
}
case ESYMBOLTYPE_VECTOR:
{
CVector **pp_vectors=(CVector**)p_dest->GetArrayPointer();
// Skip over the ESCRIPTTOKEN_STARTARRAY
++p_token;
p_token=DoAnyRandomsOrJumps(p_token);
while (*p_token!=ESCRIPTTOKEN_ENDARRAY)
{
switch (*p_token++)
{
case ESCRIPTTOKEN_ENDOFLINE:
case ESCRIPTTOKEN_COMMA:
break;
case ESCRIPTTOKEN_ENDOFLINENUMBER:
p_token+=4;
break;
case ESCRIPTTOKEN_VECTOR:
{
#ifdef __NOPT_ASSERT__
Dbg_MsgAssert(size_check,("Array size mismatch"));
--size_check;
#endif
CVector *p_new_vector=new CVector;
p_new_vector->mX=Read4Bytes(p_token).mFloat;
p_token+=4;
p_new_vector->mY=Read4Bytes(p_token).mFloat;
p_token+=4;
p_new_vector->mZ=Read4Bytes(p_token).mFloat;
p_token+=4;
*pp_vectors++=p_new_vector;
break;
}
default:
Dbg_MsgAssert(0,("Array elements must be of the same type, File %s, line %d\n",GetSourceFile(p_token-1),GetLineNumber(p_token-1)));
break;
}
// Execute any random operators. This has to be done each time p_token is advanced.
p_token=DoAnyRandomsOrJumps(p_token);
}
#ifdef __NOPT_ASSERT__
Dbg_MsgAssert(size_check==0,("Array size mismatch"));
#endif
// Skip over the ESCRIPTTOKEN_ENDARRAY
++p_token;
break;
}
case ESYMBOLTYPE_PAIR:
{
CPair **pp_pairs=(CPair**)p_dest->GetArrayPointer();
// Skip over the ESCRIPTTOKEN_STARTARRAY
++p_token;
p_token=DoAnyRandomsOrJumps(p_token);
while (*p_token!=ESCRIPTTOKEN_ENDARRAY)
{
switch (*p_token++)
{
case ESCRIPTTOKEN_ENDOFLINE:
case ESCRIPTTOKEN_COMMA:
break;
case ESCRIPTTOKEN_ENDOFLINENUMBER:
p_token+=4;
break;
case ESCRIPTTOKEN_PAIR:
{
#ifdef __NOPT_ASSERT__
Dbg_MsgAssert(size_check,("Array size mismatch"));
--size_check;
#endif
CPair *p_new_pair=new CPair;
p_new_pair->mX=Read4Bytes(p_token).mFloat;
p_token+=4;
p_new_pair->mY=Read4Bytes(p_token).mFloat;
p_token+=4;
*pp_pairs++=p_new_pair;
break;
}
default:
Dbg_MsgAssert(0,("Array elements must be of the same type, File %s, line %d\n",GetSourceFile(p_token-1),GetLineNumber(p_token-1)));
break;
}
// Execute any random operators. This has to be done each time p_token is advanced.
p_token=DoAnyRandomsOrJumps(p_token);
}
#ifdef __NOPT_ASSERT__
Dbg_MsgAssert(size_check==0,("Array size mismatch"));
#endif
// Skip over the ESCRIPTTOKEN_ENDARRAY
++p_token;
break;
}
default:
Dbg_MsgAssert(0,("Unsupported array type, File %s, line %d\n",GetSourceFile(p_token),GetLineNumber(p_token)));
break;
}
return p_token;
}
// Adds one component to p_dest.
// The component is given the name nameChecksum, and it's type and value is defined by whatever
// p_token is pointing to.
// p_args contains the structure defining the value of any args referred to using the <,> operators.
// Returns a pointer to the next token after parsing the value pointed to by p_token.
static uint8 *sAddComponentFromQB(CStruct *p_dest, uint32 nameChecksum, uint8 *p_token, CStruct *p_args)
{
Dbg_MsgAssert(p_dest,("NULL p_dest"));
Dbg_MsgAssert(p_token,("NULL p_token"));
bool use_rnd2=false;
switch (*p_token++)
{
case ESCRIPTTOKEN_NAME:
{
uint32 checksum=Read4Bytes(p_token).mChecksum;
p_token+=4;
p_dest->AddChecksum(nameChecksum,checksum);
break;
}
case ESCRIPTTOKEN_INTEGER:
{
int integer=Read4Bytes(p_token).mInt;
p_token+=4;
p_dest->AddInteger(nameChecksum,integer);
break;
}
case ESCRIPTTOKEN_KEYWORD_RANDOM_RANGE2:
use_rnd2=true;
// Intentional lack of a break here.
case ESCRIPTTOKEN_KEYWORD_RANDOM_RANGE:
{
CComponent *p_new_component=new CComponent;
p_token=sCalculateRandomRange(p_token,p_new_component,use_rnd2);
p_new_component->mNameChecksum=nameChecksum;
p_dest->AddComponent(p_new_component);
break;
}
case ESCRIPTTOKEN_FLOAT:
{
float float_val=Read4Bytes(p_token).mFloat;
p_token+=4;
p_dest->AddFloat(nameChecksum,float_val);
break;
}
case ESCRIPTTOKEN_VECTOR:
{
float x=Read4Bytes(p_token).mFloat;
p_token+=4;
float y=Read4Bytes(p_token).mFloat;
p_token+=4;
float z=Read4Bytes(p_token).mFloat;
p_token+=4;
// Alert! This next 'if' is kind of game specific ...
// It should probably be the exporter that does not include the angles in the node array if they are
// close to zero, rather than having this remove them from all structures.
// Note: We can't just remove zero angles from the node array after it has loaded, because there
// is not enough memory to store them initially.
if (nameChecksum==CRCD(0x9d2d0915,"Angles"))
{
// If the vector is close to (0,0,0) then don't bother adding it.
// This is a memory optimization. All nodes in levels are getting exported with
// an angles component, which most of the time is (0,0,0).
// This will work OK so long as whenever GetVector is used in the code to get the angles,
// the vector being loaded into is initialised with (0,0,0).
// That way, if the vector is not there, it will remain as (0,0,0) as required.
float dx=x;
if (dx<0.0f) dx=-dx;
float dy=y;
if (dy<0.0f) dy=-dy;
float dz=z;
if (dz<0.0f) dz=-dz;
if (dx<0.01f && dy<0.01f && dz<0.01f)
{
break;
}
}
p_dest->AddVector(nameChecksum,x,y,z);
break;
}
case ESCRIPTTOKEN_PAIR:
{
float x=Read4Bytes(p_token).mFloat;
p_token+=4;
float y=Read4Bytes(p_token).mFloat;
p_token+=4;
p_dest->AddPair(nameChecksum,x,y);
break;
}
case ESCRIPTTOKEN_STRING:
{
int len=Read4Bytes(p_token).mInt;
p_token+=4;
p_dest->AddString(nameChecksum,(const char *)p_token);
p_token+=len;
break;
}
case ESCRIPTTOKEN_LOCALSTRING:
{
int len=Read4Bytes(p_token).mInt;
p_token+=4;
p_dest->AddLocalString(nameChecksum,(const char *)p_token);
p_token+=len;
break;
}
case ESCRIPTTOKEN_STARTSTRUCT:
{
// Rewind p_token to point to ESCRIPTTOKEN_STARTSTRUCT, because the
// sAddComponentsWithinCurlyBraces call requires it.
--p_token;
// No need to set which heap we're using, cos CStruct's and their CComponent's come off
// their own pools.
CStruct *p_structure=new CStruct;
p_token=sAddComponentsWithinCurlyBraces(p_structure,p_token,p_args);
p_dest->AddStructurePointer(nameChecksum,p_structure);
// Note: Not deleting p_structure, because it has been given to p_dest, which will delete
// it in its destructor.
break;
}
case ESCRIPTTOKEN_STARTARRAY:
{
// Rewind p_token to point to ESCRIPTTOKEN_STARTARRAY, because the CArray::Parse call requires it.
--p_token;
// The CArray constructor is not hard-wired to use the script heap for it's buffer, so need
// to make sure we're using the script heap here.
Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().ScriptHeap());
CArray *p_array=new CArray;
Mem::Manager::sHandle().PopContext();
p_token=sInitArrayFromQB(p_array,p_token,p_args);
p_dest->AddArrayPointer(nameChecksum,p_array);
// Note: Not deleting p_array, because it has been given to p_dest, which will delete
// it in its destructor.
break;
}
case ESCRIPTTOKEN_KEYWORD_SCRIPT:
{
// When adding a local script defined in a structure, we'd expect the nameChecksum passed to
// this function to be zero, since the name comes after the script keyword.
Dbg_MsgAssert(nameChecksum==0,("nameChecksum expected to be 0 for a local script ??, File %s, line %d\n",GetSourceFile(p_token-1),GetLineNumber(p_token-1)));
Dbg_MsgAssert(*p_token==ESCRIPTTOKEN_NAME,("\nKeyword 'script' must be followed by a name, File %s, line %d\n",GetSourceFile(p_token-1),GetLineNumber(p_token-1)));
++p_token; // Skip over the ESCRIPTTOKEN_NAME
nameChecksum=Read4Bytes(p_token).mChecksum;
p_token+=4; // Skip over the name checksum
// Skip p_token over the script tokens, and calculate the size.
const uint8 *p_script=p_token;
p_token=SkipOverScript(p_token);
uint32 size=p_token-p_script;
// This will create a new buffer and copy in the script data.
p_dest->AddScript(nameChecksum,p_script,size);
break;
}
case ESCRIPTTOKEN_ARG:
{
Dbg_MsgAssert(*p_token==ESCRIPTTOKEN_NAME,("Expected '<' token to be followed by a name, File %s, line %d",GetSourceFile(p_token-1),GetLineNumber(p_token-1)));
++p_token;
uint32 arg_checksum=Read4Bytes(p_token).mChecksum;
p_token+=4;
if (p_args)
{
// Look for a parameter named arg_checksum in p_args, recursing into any referenced structures
CComponent *p_comp=p_args->FindNamedComponentRecurse(arg_checksum);
if (p_comp)
{
if (nameChecksum==0 && p_comp->mType==ESYMBOLTYPE_STRUCTURE)
{
// Trying to add an unnamed structure, in which case it's contents
// should be merged onto p_dest.
p_dest->AppendStructure(p_comp->mpStructure);
// Note: OK to call AppendStructure with NULL, so no need to check mpStructure.
}
else
{
// Create a copy of the component and add it to p_dest.
CComponent *p_new_component=new CComponent;
// Note: There is no copy-constructor for CComponent, because that would cause a
// cyclic dependency between component.cpp/h and struct.cpp/h.
// Instead, use the CopyComponent function defined in struct.cpp
CopyComponent(p_new_component,p_comp);
// Then change it's name to nameChecksum
p_new_component->mNameChecksum=nameChecksum;
// And add it to p_dest
p_dest->AddComponent(p_new_component);
}
}
}
// Don't assert if p_args is NULL, cos it will be when this function is used to skip over
// parameter lists in FindReferences
break;
}
case ESCRIPTTOKEN_KEYWORD_ALLARGS:
{
// Don't assert if p_args is NULL, cos it will be when this function is used to skip over
// parameter lists in FindReferences
if (p_args)
{
if (nameChecksum)
{
// If it's a named structure, copy the passed arguments into a new structure and insert a
// pointer to it.
CStruct *p_structure=new CStruct;
*p_structure=*p_args;
p_dest->AddStructurePointer(nameChecksum,p_structure);
}
else
{
// Otherwise just merge in the passed parameters.
// Un-named structures are always considered 'part of' the structure that they are in,
// eg {x=9 {y=3}} is the same as {x=9 y=3}
*p_dest+=*p_args;
}
}
break;
}
case ESCRIPTTOKEN_COMMA:
break;
default:
--p_token;
Dbg_MsgAssert(0,("\nBad token '%s' encountered when creating component, File %s, line %d",GetTokenName((EScriptToken)*p_token),GetSourceFile(p_token),GetLineNumber(p_token)));
break;
}
return p_token;
}
// Parses the components in QB format pointed to by p_token, and adds them to p_dest until the
// close-curly-brace token is reached.
// p_token must initially point to an open-curly-brace token.
// Any end-of-lines in between are ignored.
// For example, p_token may point to: {x=9 foo="Blaa"}
// Used when creating sub-structures and global structures.
// p_args contains the structure defining the value of any args referred to using the <,> operators.
// Returns a pointer to the next token after parsing the tokens pointed to by p_token.
static uint8 *sAddComponentsWithinCurlyBraces(CStruct *p_dest, uint8 *p_token, CStruct *p_args)
{
Dbg_MsgAssert(p_dest,("NULL p_dest"));
Dbg_MsgAssert(p_token,("NULL p_token"));
Dbg_MsgAssert(*p_token==ESCRIPTTOKEN_STARTSTRUCT,("p_token expected to point to ESCRIPTTOKEN_STARTSTRUCT, File %s, line %d",GetSourceFile(p_token),GetLineNumber(p_token)));
// Skip over the ESCRIPTTOKEN_STARTSTRUCT
++p_token;
while (true)
{
p_token=SkipEndOfLines(p_token);
// Execute any random operators. This has to be done each time p_token is advanced.
p_token=DoAnyRandomsOrJumps(p_token);
if (*p_token==ESCRIPTTOKEN_ENDSTRUCT)
{
break;
}
switch (*p_token)
{
case ESCRIPTTOKEN_NAME:
{
uint8 *p_name_token=p_token;
++p_token;
uint32 name_checksum=Read4Bytes(p_token).mChecksum;
p_token+=4;
p_token=SkipEndOfLines(p_token);
p_token=DoAnyRandomsOrJumps(p_token);
if (*p_token==ESCRIPTTOKEN_EQUALS)
{
++p_token;
p_token=SkipEndOfLines(p_token);
p_token=DoAnyRandomsOrJumps(p_token);
if (*p_token==ESCRIPTTOKEN_OPENPARENTH)
{
CComponent *p_comp=new CComponent;
p_token=Evaluate(p_token,p_args,p_comp);
if (p_comp->mType!=ESYMBOLTYPE_NONE)
{
p_comp->mNameChecksum=name_checksum;
p_dest->AddComponent(p_comp);
}
else
{
delete p_comp;
}
}
else
{
p_token=sAddComponentFromQB(p_dest,name_checksum,p_token,p_args);
}
}
else
{
p_token=sAddComponentFromQB(p_dest,NO_NAME,p_name_token,p_args);
}
break;
}
case ESCRIPTTOKEN_STARTSTRUCT:
p_token=sAddComponentsWithinCurlyBraces(p_dest,p_token,p_args);
break;
case ESCRIPTTOKEN_COMMA:
++p_token;
break;
case ESCRIPTTOKEN_ENDOFLINE:
case ESCRIPTTOKEN_ENDOFLINENUMBER:
p_token=SkipEndOfLines(p_token);
break;
default:
{
if (*p_token==ESCRIPTTOKEN_OPENPARENTH)
{
CComponent *p_comp=new CComponent;
p_token=Evaluate(p_token,p_args,p_comp);
if (p_comp->mType==ESYMBOLTYPE_STRUCTURE)
{
// The CStruct::AddComponent function does not allow unnamed structure
// components to be added, so merge in the contents of the structure instead.
p_dest->AppendStructure(p_comp->mpStructure);
// Now p_comp does have to be cleaned up and deleted, because it has not
// been given to p_dest.
CleanUpComponent(p_comp);
delete p_comp;
}
else
{
if (p_comp->mType!=ESYMBOLTYPE_NONE)
{
p_comp->mNameChecksum=NO_NAME;
p_dest->AddComponent(p_comp);
}
else
{
delete p_comp;
}
}
}
else
{
p_token=sAddComponentFromQB(p_dest,NO_NAME,p_token,p_args);
}
break;
}
}
}
// Skip over the ESCRIPTTOKEN_ENDSTRUCT
++p_token;
return p_token;
}
// Creates a new script symbol entry, allocates memory for the script data and copies it in, prefixing
// the data with the contents checksum.
static CSymbolTableEntry *sCreateScriptSymbol(uint32 nameChecksum, uint32 contentsChecksum, const uint8 *p_data, uint32 size, const char *p_fileName)
{
Dbg_MsgAssert(p_data,("NULL p_data ??"));
Dbg_MsgAssert(p_fileName,("NULL p_fileName"));
#ifdef __NOPT_ASSERT__
#ifndef __PLAT_WN32__
CheckForPossibleInfiniteLoops(nameChecksum,(uint8*)(uint32)p_data, p_fileName);
#endif
#endif
CSymbolTableEntry *p_new=CreateNewSymbolEntry(nameChecksum);
Dbg_MsgAssert(p_new,("NULL p_new ??"));
p_new->mType=ESYMBOLTYPE_QSCRIPT;
#ifdef NO_SCRIPT_CACHING
// Allocate space for the content checksum (4 bytes) plus the script data.
uint8 *p_new_script=(uint8*)Mem::Malloc(4+size);
// Write in the contents checksum.
// p_new_script will be long-word aligned so OK to cast to a uint32*
*(uint32*)p_new_script=contentsChecksum;
uint8 *p_dest=p_new_script+4;
const uint8 *p_source=p_data;
for (uint32 i=0; i<size; ++i)
{
*p_dest++=*p_source++;
}
p_new->mpScript=p_new_script;
PreProcessScript(p_new_script+4);
#else
enum
{
COMPRESS_BUFFER_SIZE=20000,
};
uint8 *p_compress_buffer=(uint8*)Mem::Malloc(COMPRESS_BUFFER_SIZE);
// Compress the script data.
int compressed_size=Encode((char*)p_data,(char*)p_compress_buffer,size,false);
Dbg_MsgAssert(compressed_size <= COMPRESS_BUFFER_SIZE,("Compress buffer overflow! Compressed size of script %s is %d",Script::FindChecksumName(nameChecksum),compressed_size));
// If it compressed to a bigger size, replace the compressed
// data with a copy of the original instead.
if (compressed_size >= (int)size)
{
const uint8 *p_source=p_data;
uint8 *p_dest=p_compress_buffer;
for (uint32 i=0; i<size; ++i)
{
*p_dest++=*p_source++;
}
compressed_size=size;
}
// Allocate space for the content checksum, decompressed size, compressed size, and the compressed data.
uint8 *p_new_script=(uint8*)Mem::Malloc(SCRIPT_HEADER_SIZE+compressed_size);
// p_new_script will be long-word aligned so OK to cast to a uint32*
*(uint32*)(p_new_script+0)=contentsChecksum;
*(uint32*)(p_new_script+4)=size;
*(uint32*)(p_new_script+8)=compressed_size;
uint8 *p_dest=p_new_script+SCRIPT_HEADER_SIZE;
const uint8 *p_source=p_compress_buffer;
for (int i=0; i<compressed_size; ++i)
{
*p_dest++=*p_source++;
}
#ifdef __PLAT_NGC__
p_new->mScriptOffset = NsARAM::alloc( SCRIPT_HEADER_SIZE+compressed_size, NsARAM::SCRIPT );
NsDMA::toARAM( p_new->mScriptOffset, p_new_script, SCRIPT_HEADER_SIZE+compressed_size );
Mem::Free( p_new_script );
#else
p_new->mpScript=p_new_script;
#endif // __PLAT_NGC__
Mem::Free(p_compress_buffer);
// Now that the new script has been loaded, the script cache needs to be refreshed in case any existing
// CScript's are running this script. They will all get restarted later (see file.cpp)
Script::CScriptCache *p_script_cache=Script::CScriptCache::Instance();
// Not asserting if p_script_cache is NULL, because it will be when all the q-files are loaded on startup.
if (p_script_cache)
{
p_script_cache->RefreshAfterReload(nameChecksum);
}
#endif
// Store the name of the source qb in the symbol so that the qb is able to be unloaded.
p_new->mSourceFileNameChecksum=Crc::GenerateCRCFromString(p_fileName);
return p_new;
}
// Creates a symbol defined by a name, followed by equals, followed by a value.
static uint8 *sCreateSymbolOfTheFormNameEqualsValue(uint8 *p_token, const char *p_fileName, EBoolAssertIfDuplicateSymbols assertIfDuplicateSymbols)
{
Dbg_MsgAssert(p_token,("NULL p_token"));
Dbg_MsgAssert(p_fileName,("NULL p_fileName"));
Dbg_MsgAssert(*p_token==ESCRIPTTOKEN_NAME,("\nExpected ESCRIPTTOKEN_NAME"));
++p_token;
uint32 name_checksum=Read4Bytes(p_token).mChecksum;
p_token+=4;
p_token=SkipEndOfLines(p_token);
Dbg_MsgAssert(*p_token==ESCRIPTTOKEN_EQUALS,("\nExpected an equals sign to follow '%s' at line %d, %s\nThe most likely explanation is that you have a command\noutside of a script ... endscript",FindChecksumName(name_checksum),GetLineNumber(p_token),p_fileName));
++p_token;
p_token=SkipEndOfLines(p_token);
// We have a name followed by an equals, so this is defining some symbol with that name,
// eg Foo=6, or Foo="Blaa"
// So first of all, see if any symbol already exists with that name, and if it does then
// remove it, or assert.
CSymbolTableEntry *p_existing_entry=LookUpSymbol(name_checksum);
if (p_existing_entry)
{
if (assertIfDuplicateSymbols)
{
Dbg_MsgAssert(0,("The symbol '%s' is defined twice, in %s and %s\n Try running CleanAss ",FindChecksumName(name_checksum),FindChecksumName(p_existing_entry->mSourceFileNameChecksum),p_fileName));
}
CleanUpAndRemoveSymbol(p_existing_entry);
}
// Create a new symbol with the given name.
CSymbolTableEntry *p_new=CreateNewSymbolEntry(name_checksum);
Dbg_MsgAssert(p_new,("NULL p_new ??"));
// Store the name of the source qb in the symbol so that the qb is able to be unloaded.
// Note: This used to be after the switch statement below. Moved it here so that if any
// assert goes off in the switch statement the file name info will be present in the symbol.
p_new->mSourceFileNameChecksum=Crc::GenerateCRCFromString(p_fileName);
// Now see what type of value follows the equals, and fill in the new symbol accordingly.
switch (*p_token)
{
case ESCRIPTTOKEN_OPENPARENTH:
{
CComponent *p_comp=new CComponent;
p_token=Evaluate(p_token,NULL,p_comp);
Dbg_MsgAssert(p_comp->mType!=ESYMBOLTYPE_NONE,("Global symbol '%s' did not evaluate to anything ...",FindChecksumName(p_comp->mNameChecksum)));
p_new->mType=p_comp->mType;
p_new->mUnion=p_comp->mUnion;
p_comp->mUnion=0;
delete p_comp;
break;
}
case ESCRIPTTOKEN_INTEGER:
case ESCRIPTTOKEN_FLOAT:
case ESCRIPTTOKEN_VECTOR:
case ESCRIPTTOKEN_PAIR:
case ESCRIPTTOKEN_STRING:
case ESCRIPTTOKEN_LOCALSTRING:
case ESCRIPTTOKEN_NAME:
{
CComponent *p_comp=new CComponent;
p_token=FillInComponentUsingQB(p_token,NULL,p_comp);
p_new->mType=p_comp->mType;
p_new->mUnion=p_comp->mUnion;
p_comp->mUnion=0;
delete p_comp;
break;
}
case ESCRIPTTOKEN_STARTSTRUCT:
{
p_new->mType=ESYMBOLTYPE_STRUCTURE;
p_new->mpStructure=new CStruct;
p_token=sAddComponentsWithinCurlyBraces(p_new->mpStructure,p_token);
break;
}
case ESCRIPTTOKEN_STARTARRAY:
{
p_new->mType=ESYMBOLTYPE_ARRAY;
p_new->mpArray=new CArray;
p_token=sInitArrayFromQB(p_new->mpArray,p_token);
break;
}
default:
Dbg_MsgAssert(0,("\nUnexpected type value of %d given to symbol '%s', line %d of %s",*p_token,FindChecksumName(name_checksum),GetLineNumber(p_token),p_fileName));
break;
} // switch (*p_token)
return p_token;
}
// Given a pointer to an un-preprocessed script, this will parse through it converting function names
// to either cfunction pointers or member function tokens.
void PreProcessScript(uint8 *p_token)
{
// Skip over the default params
p_token=SkipToStartOfNextLine(p_token);
while (*p_token!=ESCRIPTTOKEN_KEYWORD_ENDSCRIPT)
{
switch (*p_token)
{
case ESCRIPTTOKEN_KEYWORD_IF:
++p_token;
break;
case ESCRIPTTOKEN_NAME:
{
++p_token;
uint32 name_checksum=Read4Bytes(p_token).mChecksum;
p_token+=4;
// Look up the name to see if it is a cfunction or member function.
CSymbolTableEntry *p_entry=Resolve(name_checksum);
// Must not assert if p_entry is NULL, cos they might just be loading in
// a qb file that refers to a script that has not been written yet.
if (p_entry)
{
if (p_entry->mType==ESYMBOLTYPE_CFUNCTION)
{
// Change the token type and replace the checksum with the
// function pointer to save having to resolve it later.
*(p_token-5)=ESCRIPTTOKEN_RUNTIME_CFUNCTION;
Dbg_MsgAssert(p_entry->mpCFunction,("NULL p_entry->mpCFunction"));
Write4Bytes(p_token-4, (uint32)p_entry->mpCFunction);
}
else if (p_entry->mType==ESYMBOLTYPE_MEMBERFUNCTION)
{
// Saves having to look up the checksum later to find out that it is
// a member function.
*(p_token-5)=ESCRIPTTOKEN_RUNTIME_MEMBERFUNCTION;
}
}
p_token=SkipToStartOfNextLine(p_token);
break;
}
default:
p_token=SkipToStartOfNextLine(p_token);
break;
}
}
}
static CStoredRandom *sFindStoredRandom(const uint8 *p_token, EScriptToken type, int numItems)
{
CScript *p_current_script=GetCurrentScript();
CStoredRandom *p_search=sp_first_stored_random;
while (p_search)
{
if (p_search->mpToken==p_token &&
p_search->mType==type &&
p_search->mNumItems==numItems)
{
if (p_search->mpScript==p_current_script || p_search->mpScript==NULL)
{
return p_search;
}
}
p_search=p_search->mpNext;
}
return NULL;
}
static CStoredRandom *sCreateNewStoredRandom()
{
if (s_num_stored_randoms>=MAX_STORED_RANDOMS)
{
Dbg_MsgAssert(sp_last_stored_random,("NULL sp_last_stored_random ?"));
CStoredRandom *p_new_last=sp_last_stored_random->mpPrevious;
delete sp_last_stored_random;
sp_last_stored_random=p_new_last;
if (p_new_last)
{
p_new_last->mpNext=NULL;
}
}
CStoredRandom *p_new=new CStoredRandom;
p_new->mpNext=sp_first_stored_random;
p_new->mpPrevious=NULL;
if (sp_first_stored_random)
{
sp_first_stored_random->mpPrevious=p_new;
}
else
{
Dbg_MsgAssert(sp_last_stored_random==NULL,("sp_last_stored_random not NULL?"));
sp_last_stored_random=p_new;
}
sp_first_stored_random=p_new;
return p_new;
}
S4Bytes Read4Bytes(const uint8 *p_long)
{
S4Bytes four_bytes;
#ifdef __PLAT_NGPS__
if ( (((uint32)p_long)&3)==0 )
{
// Small speed opt
four_bytes.mUInt=*(uint32*)p_long;
}
else
{
four_bytes.mUInt=(p_long[0])|(p_long[1]<<8)|(p_long[2]<<16)|(p_long[3]<<24);
}
#else
four_bytes.mUInt=(p_long[0])|(p_long[1]<<8)|(p_long[2]<<16)|(p_long[3]<<24);
#endif
return four_bytes;
}
// Gets 2 bytes from p_short, which may not be long word aligned.
S2Bytes Read2Bytes(const uint8 *p_short)
{
S2Bytes two_bytes;
two_bytes.mUInt=p_short[0]|(p_short[1]<<8);
return two_bytes;
}
// Used by WriteToBuffer and ReadFromBuffer in utils.cpp
// Moved to parse.cpp just so that all these byte reading/writing functions are in one place.
uint8 *Write4Bytes(uint8 *p_buffer, uint32 val)
{
*p_buffer++=val;
*p_buffer++=val>>8;
*p_buffer++=val>>16;
*p_buffer++=val>>24;
return p_buffer;
}
uint8 *Write4Bytes(uint8 *p_buffer, float floatVal)
{
uint32 val=*(uint32*)&floatVal;
*p_buffer++=val;
*p_buffer++=val>>8;
*p_buffer++=val>>16;
*p_buffer++=val>>24;
return p_buffer;
}
uint8 *Write2Bytes(uint8 *p_buffer, uint16 val)
{
*p_buffer++=val;
*p_buffer++=val>>8;
return p_buffer;
}
// Skips over end-of-line tokens.
uint8 *SkipEndOfLines(uint8 *p_token)
{
Dbg_MsgAssert(p_token,("NULL p_token"));
while (true)
{
if (*p_token==ESCRIPTTOKEN_ENDOFLINE)
{
++p_token;
}
else if (*p_token==ESCRIPTTOKEN_ENDOFLINENUMBER)
{
// ESCRIPTTOKEN_ENDOFLINENUMBER contains the line number of the previous line,
// so that the line number of errors can be displayed.
p_token+=5;
}
else
{
break;
}
}
return p_token;
}
CStoredRandom::CStoredRandom()
{
mpNext=NULL;
mpPrevious=NULL;
++s_num_stored_randoms;
}
CStoredRandom::~CStoredRandom()
{
--s_num_stored_randoms;
}
// This gets called whenever CScript::ClearScript is called.
void ReleaseStoredRandoms(CScript *p_script)
{
CStoredRandom *p_search=sp_first_stored_random;
while (p_search)
{
if (p_search->mpScript==p_script)
{
p_search->mpScript=NULL;
}
p_search=p_search->mpNext;
}
}
void CStoredRandom::InitIndices()
{
Dbg_MsgAssert(mNumItems<=MAX_RANDOM_INDICES,("mNumItems too big"));
for (uint16 i=0; i<mNumItems; ++i)
{
mpIndices[i]=i;
}
}
void CStoredRandom::RandomizeIndices(bool makeNewFirstDifferFromOldLast)
{
Dbg_MsgAssert(mNumItems>=2,("mNumItems must be at least 2 for RandomNoRepeat"));
uint8 last=mpIndices[mNumItems-1];
uint32 num_iterations=10*mNumItems;
for (uint32 n=0; n<num_iterations; ++n)
{
int a=Mth::Rnd(mNumItems);
int b=Mth::Rnd(mNumItems);
uint8 temp=mpIndices[a];
mpIndices[a]=mpIndices[b];
mpIndices[b]=temp;
}
if (makeNewFirstDifferFromOldLast && mpIndices[0]==last)
{
int a=0;
int b=1+Mth::Rnd(mNumItems-1);
uint8 temp=mpIndices[a];
mpIndices[a]=mpIndices[b];
mpIndices[b]=temp;
}
}
// If p_token points to a random or jump token, then this will execute it,
// and repeat until no more randoms or jumps.
//
// Returns the new p_token, which is all this function modifies.
uint8 *DoAnyRandomsOrJumps(uint8 *p_token)
{
Dbg_MsgAssert(p_token,("NULL p_token"));
while (true)
{
bool finished=false;
uint8 token=*p_token;
switch (token)
{
case ESCRIPTTOKEN_KEYWORD_RANDOM:
case ESCRIPTTOKEN_KEYWORD_RANDOM2:
{
++p_token; // Skip over the token.
uint32 num_items=Read4Bytes(p_token).mUInt;
Dbg_MsgAssert(num_items,("Zero num_items in random operator ?"));
p_token+=4; // Skip over the num_items
// Calculate the sum of the weights
int sum_of_weights=0;
uint8 *p_weights=p_token;
for (uint32 i=0; i<num_items; ++i)
{
sum_of_weights+=Read2Bytes(p_weights).mInt;
p_weights+=2;
}
Dbg_MsgAssert(sum_of_weights<=32767,("sum_of_weights too big"));
int r;
if (token==ESCRIPTTOKEN_KEYWORD_RANDOM)
{
r=Mth::Rnd(sum_of_weights);
}
else
{
r=Mth::Rnd2(sum_of_weights);
}
uint32 chosen_index=0;
p_weights=p_token;
while (true)
{
r-=Read2Bytes(p_weights).mInt;
if (r<0)
{
break;
}
p_weights+=2;
++chosen_index;
Dbg_MsgAssert(chosen_index<num_items,("chosen_index >= num_items ???"));
}
p_token+=2*num_items; // Skip over the weights
p_token+=4*chosen_index; // Jump to the chosen offset value
sint32 offset=Read4Bytes(p_token).mInt; // Get the offset
p_token+=4; // Skip over the offset
p_token+=offset; // Jump to the item
break;
}
case ESCRIPTTOKEN_KEYWORD_RANDOM_NO_REPEAT:
{
++p_token; // Skip over the token.
uint32 num_items=Read4Bytes(p_token).mUInt;
Dbg_MsgAssert(num_items>1,("num_items must be greater than 1 in RandomNoRepeat operator"));
p_token+=4; // Skip over the num_items
p_token+=2*num_items; // TODO: Skip over weights
CStoredRandom *p_stored=sFindStoredRandom(p_token,ESCRIPTTOKEN_KEYWORD_RANDOM_NO_REPEAT,num_items);
if (!p_stored)
{
p_stored=sCreateNewStoredRandom();
Dbg_MsgAssert(p_stored,("NULL return value from sCreateNewStoredRandom()"));
p_stored->mpToken=p_token;
p_stored->mType=ESCRIPTTOKEN_KEYWORD_RANDOM_NO_REPEAT;
p_stored->mNumItems=num_items;
p_stored->mLastIndex=Mth::Rnd(num_items);
}
p_stored->mpScript=GetCurrentScript();
uint16 jump_index=Mth::Rnd(num_items);
// num_items is bigger than one, so in theory it should never get stuck in
// an infinite loop here ...
int c=0; // But have a counter just in case
while (jump_index==p_stored->mLastIndex)
{
++c;
if (c>100) break;
jump_index=Mth::Rnd(num_items);
}
p_stored->mLastIndex=jump_index;
p_token+=4*jump_index; // Jump to the offset
sint32 offset=Read4Bytes(p_token).mInt; // Get the offset
p_token+=4; // Skip over the offset
p_token+=offset; // Jump to the item
break;
}
case ESCRIPTTOKEN_KEYWORD_RANDOM_PERMUTE:
{
++p_token; // Skip over the token.
uint32 num_items=Read4Bytes(p_token).mUInt;
Dbg_MsgAssert(num_items,("Zero num_items in random operator ?"));
p_token+=4; // Skip over the num_items
p_token+=2*num_items; // TODO: Skip over weights
CStoredRandom *p_stored=sFindStoredRandom(p_token,ESCRIPTTOKEN_KEYWORD_RANDOM_PERMUTE,num_items);
if (!p_stored)
{
p_stored=sCreateNewStoredRandom();
Dbg_MsgAssert(p_stored,("NULL return value from sCreateNewStoredRandom()"));
p_stored->mpToken=p_token;
p_stored->mType=ESCRIPTTOKEN_KEYWORD_RANDOM_PERMUTE;
p_stored->mNumItems=num_items;
p_stored->mCurrentIndex=0;
Dbg_MsgAssert(num_items<=MAX_RANDOM_INDICES,("Too many entries in RandomPermute, max is %d. Line %d of file %s",MAX_RANDOM_INDICES,GetLineNumber(p_token),GetSourceFile(p_token)));
p_stored->InitIndices();
p_stored->RandomizeIndices();
}
p_stored->mpScript=GetCurrentScript();
++p_stored->mCurrentIndex;
if (p_stored->mCurrentIndex>=p_stored->mNumItems)
{
p_stored->RandomizeIndices(MAKE_NEW_FIRST_DIFFER_FROM_OLD_LAST);
p_stored->mCurrentIndex=0;
}
uint16 jump_index=p_stored->mpIndices[p_stored->mCurrentIndex];
Dbg_MsgAssert(jump_index<num_items,("Eh? jump_index out of range ?"));
p_token+=4*jump_index; // Jump to the offset
sint32 offset=Read4Bytes(p_token).mInt; // Get the offset
p_token+=4; // Skip over the offset
p_token+=offset; // Jump to the item
break;
}
case ESCRIPTTOKEN_JUMP:
{
++p_token; // Skip over the jump token.
sint32 offset=Read4Bytes(p_token).mInt;
p_token+=4; // Skip over the offset
p_token+=offset; // Do the jump
break;
}
default:
finished=true;
break;
}
if (finished)
{
break;
}
}
return p_token;
}
// Returns the pointer to after the script.
uint8 *SkipOverScript(uint8 *p_token)
{
Dbg_MsgAssert(p_token,("NULL p_token"));
while (*p_token!=ESCRIPTTOKEN_KEYWORD_ENDSCRIPT)
{
p_token=SkipToken(p_token);
}
p_token=SkipToken(p_token); // Skip over the ESCRIPTTOKEN_KEYWORD_ENDSCRIPT
// Include any line number info following the endscript. This is for use by the script
// debugger, so that GetLineNumber will still work if the passed pointer points to an
// endscript token.
if (*p_token==ESCRIPTTOKEN_ENDOFLINENUMBER)
{
p_token=SkipToken(p_token);
}
return p_token;
}
// Calculates the checksum of a script, but ignores end-of-line tokens.
// This is used when reloading a script to detect whether the script has changed. If a script has
// changed, any instances of that script that are running will get restarted.
// Want to ignore end-of-lines and end-of-line-number tokens so that inserting or
// deleting lines won't cause all the scripts after that line to restart.
uint32 CalculateScriptContentsChecksum(uint8 *p_token)
{
Dbg_MsgAssert(p_token,("NULL p_token"));
uint32 checksum=0xffffffff;
while (*p_token != ESCRIPTTOKEN_KEYWORD_ENDSCRIPT)
{
uint8 *p_last_token=p_token;
p_token=SkipToken(p_token);
// Only include non end-of-line tokens in the checksum calculation.
if (*p_last_token != ESCRIPTTOKEN_ENDOFLINE &&
*p_last_token != ESCRIPTTOKEN_ENDOFLINENUMBER)
{
int num_bytes=p_token-p_last_token;
checksum=Crc::UpdateCRC((const char*)p_last_token,num_bytes,checksum);
}
}
return checksum;
}
// Given a token pointer, this will return the line number in the source q file.
// If it can't find a line number it returns 0. (Valid line numbers start at 1)
// If it gets to the end of the file it returns -1.
// If it gets to the end of a script it returns -2.
//
// Note: Perhaps make this compile to nothing for X-Box, since it is often used in asserts and I think
// the X-box will always execute functions in asserts, making parsing very slow. (Check with Dave)
int GetLineNumber(uint8 *p_token)
{
if (!p_token)
{
return 0;
}
switch (*p_token)
{
case ESCRIPTTOKEN_ENDOFFILE:
case ESCRIPTTOKEN_ENDOFLINE:
case ESCRIPTTOKEN_ENDOFLINENUMBER:
case ESCRIPTTOKEN_STARTSTRUCT:
case ESCRIPTTOKEN_ENDSTRUCT:
case ESCRIPTTOKEN_STARTARRAY:
case ESCRIPTTOKEN_ENDARRAY:
case ESCRIPTTOKEN_EQUALS:
case ESCRIPTTOKEN_DOT:
case ESCRIPTTOKEN_COMMA:
case ESCRIPTTOKEN_MINUS:
case ESCRIPTTOKEN_ADD:
case ESCRIPTTOKEN_DIVIDE:
case ESCRIPTTOKEN_MULTIPLY:
case ESCRIPTTOKEN_OPENPARENTH:
case ESCRIPTTOKEN_CLOSEPARENTH:
case ESCRIPTTOKEN_DEBUGINFO:
case ESCRIPTTOKEN_SAMEAS:
case ESCRIPTTOKEN_LESSTHAN:
case ESCRIPTTOKEN_LESSTHANEQUAL:
case ESCRIPTTOKEN_GREATERTHAN:
case ESCRIPTTOKEN_GREATERTHANEQUAL:
case ESCRIPTTOKEN_NAME:
case ESCRIPTTOKEN_INTEGER:
case ESCRIPTTOKEN_HEXINTEGER:
case ESCRIPTTOKEN_ENUM:
case ESCRIPTTOKEN_FLOAT:
case ESCRIPTTOKEN_STRING:
case ESCRIPTTOKEN_LOCALSTRING:
case ESCRIPTTOKEN_ARRAY:
case ESCRIPTTOKEN_VECTOR:
case ESCRIPTTOKEN_PAIR:
case ESCRIPTTOKEN_KEYWORD_BEGIN:
case ESCRIPTTOKEN_KEYWORD_REPEAT:
case ESCRIPTTOKEN_KEYWORD_BREAK:
case ESCRIPTTOKEN_KEYWORD_SCRIPT:
case ESCRIPTTOKEN_KEYWORD_ENDSCRIPT:
case ESCRIPTTOKEN_KEYWORD_IF:
case ESCRIPTTOKEN_KEYWORD_ELSE:
case ESCRIPTTOKEN_KEYWORD_ELSEIF:
case ESCRIPTTOKEN_KEYWORD_ENDIF:
case ESCRIPTTOKEN_KEYWORD_RETURN:
case ESCRIPTTOKEN_KEYWORD_NOT:
case ESCRIPTTOKEN_KEYWORD_SWITCH:
case ESCRIPTTOKEN_KEYWORD_ENDSWITCH:
case ESCRIPTTOKEN_KEYWORD_CASE:
case ESCRIPTTOKEN_KEYWORD_DEFAULT:
case ESCRIPTTOKEN_UNDEFINED:
case ESCRIPTTOKEN_CHECKSUM_NAME:
case ESCRIPTTOKEN_KEYWORD_ALLARGS:
case ESCRIPTTOKEN_ARG:
case ESCRIPTTOKEN_JUMP:
case ESCRIPTTOKEN_KEYWORD_RANDOM:
case ESCRIPTTOKEN_KEYWORD_RANDOM2:
case ESCRIPTTOKEN_KEYWORD_RANDOM_NO_REPEAT:
case ESCRIPTTOKEN_KEYWORD_RANDOM_PERMUTE:
case ESCRIPTTOKEN_OR:
case ESCRIPTTOKEN_AND:
case ESCRIPTTOKEN_XOR:
case ESCRIPTTOKEN_SHIFT_LEFT:
case ESCRIPTTOKEN_SHIFT_RIGHT:
case ESCRIPTTOKEN_KEYWORD_RANDOM_RANGE:
case ESCRIPTTOKEN_KEYWORD_RANDOM_RANGE2:
case ESCRIPTTOKEN_COLON:
case ESCRIPTTOKEN_RUNTIME_CFUNCTION:
case ESCRIPTTOKEN_RUNTIME_MEMBERFUNCTION:
break;
default:
Dbg_MsgAssert(0,("p_token does not point to a token in call to GetLineNumber"));
break;
}
while (true)
{
switch (*p_token)
{
case ESCRIPTTOKEN_ENDOFLINE:
return 0;
break;
case ESCRIPTTOKEN_ENDOFLINENUMBER:
++p_token;
return Read4Bytes(p_token).mInt;
break;
case ESCRIPTTOKEN_ENDOFFILE:
return -1;
break;
case ESCRIPTTOKEN_KEYWORD_ENDSCRIPT:
p_token=SkipToken(p_token);
if (*p_token==ESCRIPTTOKEN_ENDOFLINENUMBER)
{
// Continue so that the ENDOFLINENUMBER case catches this next time round the loop.
}
else
{
// Cannot continue searching because we've gone off the end
// of the memory block allocated for the script.
return -2;
}
break;
default:
p_token=SkipToken(p_token);
break;
}
}
}
// Returns true if the passed p_token points somewhere in the passed p_script.
bool PointsIntoScript(uint8 *p_token, uint8 *p_script)
{
Dbg_MsgAssert(p_token,("NULL p_token"));
Dbg_MsgAssert(p_script,("NULL p_script"));
// Scan through the whole script to see if p_token points to
// a token in it. Blimey.
while (*p_script!=ESCRIPTTOKEN_KEYWORD_ENDSCRIPT)
{
if (p_script==p_token)
{
return true;
}
p_script=SkipToken(p_script);
}
return false;
}
// Given a pointer to a token, this will try and find out what source file this is in.
const char *GetSourceFile(uint8 *p_token)
{
// It may be that GetSourceFile was called due to an error when creating a script global.
// In this case the source file cannot be determined from p_token since the qb is loaded into
// a temporary buffer, but ParseQB will have set s_qb_being_parsed.
if (s_qb_being_parsed)
{
return FindChecksumName(s_qb_being_parsed);
}
#ifdef NO_SCRIPT_CACHING
// If there is no script caching, then the symbol table entries for each script contain pointers
// to the uncompressed scripts, so it is possible to scan through them checking if p_token points
// into one of them.
// Check if pToken points into any of the scripts currently loaded.
CSymbolTableEntry *p_sym=GetNextSymbolTableEntry(NULL);
while (p_sym)
{
// For every script ...
if (p_sym->mType==ESYMBOLTYPE_QSCRIPT)
{
uint8 *p_script=p_sym->mpScript;
Dbg_MsgAssert(p_script,("NULL p_sym->mpScript ?"));
// Skip over the contents checksum.
p_script+=4;
if (PointsIntoScript(p_token,p_script))
{
return FindChecksumName(p_sym->mSourceFileNameChecksum);
}
}
p_sym=GetNextSymbolTableEntry(p_sym);
}
#else
// Script caching is on, so query the script cache to find out what the script name is.
// Cannot scan through the symbols mpScript's as above because when script caching is
// on mpScript will point to compressed data, which cannot be stepped through.
// So ask the script cache to step through its decompressed scripts instead.
Script::CScriptCache *p_script_cache=Script::CScriptCache::Instance();
Dbg_MsgAssert(p_script_cache,("NULL p_script_cache"));
return p_script_cache->GetSourceFile(p_token);
#endif
// Oh well.
return "Unknown";
}
#if 0
void CalcSpaceUsedByLineNumberInfo()
{
int space_used=0;
CSymbolTableEntry *p_sym=GetNextSymbolTableEntry(NULL);
while (p_sym)
{
// For every script ...
if (p_sym->mType==ESYMBOLTYPE_QSCRIPT)
{
uint8 *p_script=p_sym->mpScript;
Dbg_MsgAssert(p_script,("NULL p_sym->mpScript ?"));
// Skip over the contents checksum.
p_script+=4;
// Scan through the whole script looking for line number tokens.
while (*p_script!=ESCRIPTTOKEN_KEYWORD_ENDSCRIPT)
{
if (*p_script==ESCRIPTTOKEN_ENDOFLINENUMBER)
{
space_used+=4;
}
p_script=SkipToken(p_script);
}
}
p_sym=GetNextSymbolTableEntry(p_sym);
}
printf("Script heap space used by line number info = %d bytes\n");
}
#endif
uint8 *FillInComponentUsingQB(uint8 *p_token, CStruct *p_args, CComponent *p_comp)
{
Dbg_MsgAssert(p_token,("NULL p_token"));
Dbg_MsgAssert(p_comp,("NULL p_comp"));
bool use_rnd2=false;
switch (*p_token++)
{
case ESCRIPTTOKEN_INTEGER:
p_comp->mType=ESYMBOLTYPE_INTEGER;
p_comp->mIntegerValue=Read4Bytes(p_token).mInt;
p_token+=4;
break;
case ESCRIPTTOKEN_FLOAT:
p_comp->mType=ESYMBOLTYPE_FLOAT;
p_comp->mFloatValue=Read4Bytes(p_token).mFloat;
p_token+=4;
break;
case ESCRIPTTOKEN_NAME:
p_comp->mType=ESYMBOLTYPE_NAME;
p_comp->mChecksum=Read4Bytes(p_token).mChecksum;
p_token+=4;
break;
case ESCRIPTTOKEN_STRING:
{
p_comp->mType=ESYMBOLTYPE_STRING;
int len=Read4Bytes(p_token).mInt;
p_token+=4;
p_comp->mpString=CreateString((const char *)p_token);
p_token+=len;
break;
}
case ESCRIPTTOKEN_LOCALSTRING:
{
p_comp->mType=ESYMBOLTYPE_LOCALSTRING;
int len=Read4Bytes(p_token).mInt;
p_token+=4;
p_comp->mpString=CreateString((const char *)p_token);
p_token+=len;
break;
}
case ESCRIPTTOKEN_VECTOR:
{
p_comp->mType=ESYMBOLTYPE_VECTOR;
float x=Read4Bytes(p_token).mFloat;
p_token+=4;
float y=Read4Bytes(p_token).mFloat;
p_token+=4;
float z=Read4Bytes(p_token).mFloat;
p_token+=4;
p_comp->mpVector=new CVector;
p_comp->mpVector->mX=x;
p_comp->mpVector->mY=y;
p_comp->mpVector->mZ=z;
break;
}
case ESCRIPTTOKEN_PAIR:
{
p_comp->mType=ESYMBOLTYPE_PAIR;
float x=Read4Bytes(p_token).mFloat;
p_token+=4;
float y=Read4Bytes(p_token).mFloat;
p_token+=4;
p_comp->mpPair=new CPair;
p_comp->mpPair->mX=x;
p_comp->mpPair->mY=y;
break;
}
case ESCRIPTTOKEN_STARTSTRUCT:
{
// Rewind p_token to point to ESCRIPTTOKEN_STARTSTRUCT, because the
// sAddComponentsWithinCurlyBraces call requires it.
--p_token;
// No need to set which heap we're using, cos CStruct's and their CComponent's come off
// their own pools.
CStruct *p_structure=new CStruct;
p_token=sAddComponentsWithinCurlyBraces(p_structure,p_token,p_args);
p_comp->mType=ESYMBOLTYPE_STRUCTURE;
p_comp->mpStructure=p_structure;
break;
}
case ESCRIPTTOKEN_STARTARRAY:
{
// Rewind p_token to point to ESCRIPTTOKEN_STARTARRAY, because the CArray::Parse call requires it.
--p_token;
// The CArray constructor is not hard-wired to use the script heap for it's buffer, so need
// to make sure we're using the script heap here.
Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().ScriptHeap());
CArray *p_array=new CArray;
Mem::Manager::sHandle().PopContext();
p_token=sInitArrayFromQB(p_array,p_token,p_args);
p_comp->mType=ESYMBOLTYPE_ARRAY;
p_comp->mpArray=p_array;
break;
}
case ESCRIPTTOKEN_KEYWORD_RANDOM_RANGE2:
use_rnd2=true;
// Intentional lack of a break here.
case ESCRIPTTOKEN_KEYWORD_RANDOM_RANGE:
{
p_token=sCalculateRandomRange(p_token,p_comp,use_rnd2);
break;
}
case ESCRIPTTOKEN_KEYWORD_SCRIPT:
{
Dbg_MsgAssert(*p_token==ESCRIPTTOKEN_NAME,("\nKeyword 'script' must be followed by a name, line %d (file name not known, sorry)",GetLineNumber(p_token)));
++p_token; // Skip over the ESCRIPTTOKEN_NAME
p_comp->mNameChecksum=Read4Bytes(p_token).mChecksum;
p_token+=4; // Skip over the name checksum
// Skip p_token over the script tokens, and calculate the size.
const uint8 *p_script=p_token;
p_token=SkipOverScript(p_token);
uint32 size=p_token-p_script;
p_comp->mType=ESYMBOLTYPE_QSCRIPT;
// Allocate a buffer off the script heap
Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().ScriptHeap());
Dbg_MsgAssert(size,("Zero script size"));
uint8 *p_new_script=(uint8*)Mem::Malloc(size);
Mem::Manager::sHandle().PopContext();
// Copy the script into the new buffer.
const uint8 *p_source=p_script;
uint8 *p_dest=p_new_script;
for (uint32 i=0; i<size; ++i)
{
*p_dest++=*p_source++;
}
// Give the new buffer to the component.
p_comp->mpScript=p_new_script;
break;
}
case ESCRIPTTOKEN_ARG:
{
Dbg_MsgAssert(*p_token==ESCRIPTTOKEN_NAME,("Expected '<' token to be followed by a name, File %s, line %d",GetSourceFile(p_token),GetLineNumber(p_token)));
++p_token;
uint32 arg_checksum=Read4Bytes(p_token).mChecksum;
p_token+=4;
if (p_args)
{
// Look for a parameter named arg_checksum in p_args, recursing into any referenced structures
CComponent *p_found=p_args->FindNamedComponentRecurse(arg_checksum);
if (p_found)
{
// Note: There is no copy-constructor for CComponent, because that would cause a
// cyclic dependency between component.cpp/h and struct.cpp/h.
// Instead, use the CopyComponent function defined in struct.cpp
CopyComponent(p_comp,p_found);
}
}
break;
}
case ESCRIPTTOKEN_KEYWORD_ALLARGS:
{
if (p_args)
{
CStruct *p_structure=new CStruct;
*p_structure=*p_args;
p_comp->mType=ESYMBOLTYPE_STRUCTURE;
p_comp->mpStructure=p_structure;
}
break;
}
default:
break;
}
return p_token;
}
// This will evaluate the expression pointed to by p_token.
// Any args referred to by the <,> symbols are looked for in p_args.
// Expressions are terminated by any token that does not make sense as part of the expression.
// If the expression gets terminated unexpectedly, eg if there are still open braces,
// then it will assert.
static CExpressionEvaluator sExpressionEvaluator;
void EnableExpressionEvaluatorErrorChecking()
{
sExpressionEvaluator.EnableErrorChecking();
}
void DisableExpressionEvaluatorErrorChecking()
{
sExpressionEvaluator.DisableErrorChecking();
}
static CComponent sTemp;
uint8 *Evaluate(uint8 *p_token, CStruct *p_args, CComponent *p_result)
{
Dbg_MsgAssert(p_result,("NULL p_result"));
Dbg_MsgAssert(p_token,("NULL p_token"));
// SPEEDOPT: Make the Clear function faster
sExpressionEvaluator.ClearIfNeeded();
sExpressionEvaluator.SetTokenPointer(p_token);
Dbg_MsgAssert(*p_token==ESCRIPTTOKEN_OPENPARENTH,("Expected expression to begin with a '(', at line %d of %s",GetLineNumber(p_token),GetSourceFile(p_token)));
++p_token;
int parenth_count=0;
bool generate_minus_operator=false;
bool expecting_value=true;
bool operator_is_dot=false;
bool in_expression=true;
while (in_expression)
{
p_token=DoAnyRandomsOrJumps(p_token);
uint8 token_value=*p_token;
switch (token_value)
{
case ESCRIPTTOKEN_OPENPARENTH:
if (!expecting_value)
{
in_expression=false;
break;
}
++p_token;
sExpressionEvaluator.OpenParenthesis();
generate_minus_operator=false;
expecting_value=true;
++parenth_count;
break;
case ESCRIPTTOKEN_CLOSEPARENTH:
++p_token;
if (parenth_count)
{
sExpressionEvaluator.CloseParenthesis();
generate_minus_operator=true;
expecting_value=false;
--parenth_count;
}
else
{
in_expression=false;
}
break;
case ESCRIPTTOKEN_INTEGER:
if (expecting_value)
{
Dbg_MsgAssert(!generate_minus_operator,("Eh? generate_minus_operator is set?"));
p_token=FillInComponentUsingQB(p_token,p_args,&sTemp);
sExpressionEvaluator.Input(&sTemp);
CleanUpComponent(&sTemp);
generate_minus_operator=true;
expecting_value=false;
}
else
{
uint8 *p_new_token=FillInComponentUsingQB(p_token,p_args,&sTemp);
if (sTemp.mIntegerValue<0 && generate_minus_operator)
{
sExpressionEvaluator.Input(ESCRIPTTOKEN_MINUS);
sTemp.mIntegerValue=-sTemp.mIntegerValue;
sExpressionEvaluator.Input(&sTemp);
CleanUpComponent(&sTemp);
p_token=p_new_token;
generate_minus_operator=true;
expecting_value=false;
}
else
{
in_expression=false;
}
}
break;
case ESCRIPTTOKEN_FLOAT:
if (expecting_value)
{
Dbg_MsgAssert(!generate_minus_operator,("Eh? generate_minus_operator is set?"));
p_token=FillInComponentUsingQB(p_token,p_args,&sTemp);
sExpressionEvaluator.Input(&sTemp);
CleanUpComponent(&sTemp);
generate_minus_operator=true;
expecting_value=false;
}
else
{
uint8 *p_new_token=FillInComponentUsingQB(p_token,p_args,&sTemp);
if (sTemp.mFloatValue<0.0f && generate_minus_operator)
{
sExpressionEvaluator.Input(ESCRIPTTOKEN_MINUS);
sTemp.mFloatValue=-sTemp.mFloatValue;
sExpressionEvaluator.Input(&sTemp);
CleanUpComponent(&sTemp);
p_token=p_new_token;
generate_minus_operator=true;
expecting_value=false;
}
else
{
in_expression=false;
}
}
break;
case ESCRIPTTOKEN_NAME:
{
if (!expecting_value)
{
in_expression=false;
break;
}
p_token=FillInComponentUsingQB(p_token,p_args,&sTemp);
// TODO: Use ResolveNameComponent instead of this switch ...
CSymbolTableEntry *p_entry=NULL;
if (operator_is_dot)
{
// This is a bit of a hack ...
// If the operator is the dot operator, then do not check if the right-hand-side
// is a global. This is because when the . is used to access members of a structure,
// it may be that the name of the parameter being accessed is also the name of a
// global, which if substituted in place of the name would result in a nonsense
// expression.
// The only disadvantage of this hack is that it means that a vector can no longer be
// dot producted with a global vector, at least not directly.
// I don't think there even are any global vectors at the moment anyway, and if there
// were they could still be dot-producted with by simply copying them into a parameter
// and dotting with that instead.
// The more general solution to the problem would be to have some sort of special
// syntax to indicate when a name is not to be resolved. Eg, Foo.Blaa would mean
// that Blaa would be resolved, whereas Foo.~Blaa would tell the interpreter not to
// resolve it.
// That would be more complex to implement though.
}
else
{
p_entry=Resolve(sTemp.mChecksum);
}
if (p_entry)
{
switch (p_entry->mType)
{
case ESYMBOLTYPE_FLOAT:
sTemp.mType=ESYMBOLTYPE_FLOAT;
sTemp.mFloatValue=p_entry->mFloatValue;
sExpressionEvaluator.Input(&sTemp);
break;
case ESYMBOLTYPE_INTEGER:
sTemp.mType=ESYMBOLTYPE_INTEGER;
sTemp.mIntegerValue=p_entry->mIntegerValue;
sExpressionEvaluator.Input(&sTemp);
break;
case ESYMBOLTYPE_VECTOR:
sTemp.mType=ESYMBOLTYPE_VECTOR;
sTemp.mpVector=p_entry->mpVector;
sExpressionEvaluator.Input(&sTemp);
break;
case ESYMBOLTYPE_PAIR:
sTemp.mType=ESYMBOLTYPE_PAIR;
sTemp.mpPair=p_entry->mpPair;
sExpressionEvaluator.Input(&sTemp);
break;
case ESYMBOLTYPE_STRING:
sTemp.mType=ESYMBOLTYPE_STRING;
sTemp.mpString=p_entry->mpString;
sExpressionEvaluator.Input(&sTemp);
break;
case ESYMBOLTYPE_LOCALSTRING:
sTemp.mType=ESYMBOLTYPE_LOCALSTRING;
sTemp.mpLocalString=p_entry->mpLocalString;
sExpressionEvaluator.Input(&sTemp);
break;
case ESYMBOLTYPE_STRUCTURE:
sTemp.mType=ESYMBOLTYPE_STRUCTURE;
sTemp.mpStructure=p_entry->mpStructure;
sExpressionEvaluator.Input(&sTemp);
break;
case ESYMBOLTYPE_ARRAY:
sTemp.mType=ESYMBOLTYPE_ARRAY;
sTemp.mpArray=p_entry->mpArray;
sExpressionEvaluator.Input(&sTemp);
break;
case ESYMBOLTYPE_CFUNCTION:
{
CScript *p_script=GetCurrentScript();
Dbg_MsgAssert(p_script,("Tried to execute the cfunc '%s' in an expression with no parent script, line %d of file %s",FindChecksumName(sTemp.mChecksum),GetLineNumber(p_token),GetSourceFile(p_token)));
sTemp.mType=ESYMBOLTYPE_INTEGER;
CStruct *p_function_params=new CStruct;
// TODO: Problem if AddComponentsUntilEndOfLine calls Evaluate() again,
// since only one instance of CExpressionEvaluator.
p_token=AddComponentsUntilEndOfLine(p_function_params,p_token,p_args);
sTemp.mIntegerValue=(*p_entry->mpCFunction)(p_function_params,p_script);
delete p_function_params;
sExpressionEvaluator.Input(&sTemp);
break;
}
case ESYMBOLTYPE_MEMBERFUNCTION:
Dbg_MsgAssert(0,("Sorry, cannot have calls to member functions in expressions!\nTried to call member function '%s' in an expression, line %d of file %s",FindChecksumName(sTemp.mChecksum),GetLineNumber(p_token),GetSourceFile(p_token)));
break;
default:
sExpressionEvaluator.Input(&sTemp);
break;
}
}
else
{
sExpressionEvaluator.Input(&sTemp);
}
// Must not call CleanUpComponent because any pointer in the component will
// have been borrowed from the global symbol.
sTemp.mType=ESYMBOLTYPE_NONE;
sTemp.mUnion=0;
generate_minus_operator=true;
expecting_value=false;
break;
}
case ESCRIPTTOKEN_STRING:
case ESCRIPTTOKEN_LOCALSTRING:
case ESCRIPTTOKEN_VECTOR:
case ESCRIPTTOKEN_PAIR:
//case ESCRIPTTOKEN_STARTARRAY:
case ESCRIPTTOKEN_STARTSTRUCT:
case ESCRIPTTOKEN_KEYWORD_RANDOM_RANGE:
if (!expecting_value)
{
in_expression=false;
break;
}
p_token=FillInComponentUsingQB(p_token,p_args,&sTemp);
sExpressionEvaluator.Input(&sTemp);
CleanUpComponent(&sTemp);
generate_minus_operator=true;
expecting_value=false;
break;
case ESCRIPTTOKEN_ARG:
{
if (!expecting_value)
{
in_expression=false;
break;
}
++p_token;
Dbg_MsgAssert(*p_token==ESCRIPTTOKEN_NAME,("Expected '<' token to be followed by a name, File %s, line %d",GetSourceFile(p_token),GetLineNumber(p_token)));
++p_token;
uint32 arg_checksum=Read4Bytes(p_token).mChecksum;
p_token+=4;
if (p_args)
{
// Look for a parameter named arg_checksum in p_args, recursing into any referenced structures
CComponent *p_comp=p_args->FindNamedComponentRecurse(arg_checksum);
if (p_comp)
{
sTemp.mType=p_comp->mType;
sTemp.mUnion=p_comp->mUnion;
// It might be a name that resolves to some global, so resolve it.
ResolveNameComponent(&sTemp);
sExpressionEvaluator.Input(&sTemp);
// Must not call CleanUpComponent because any pointer in the component will
// have been borrowed from the global symbol or p_comp.
sTemp.mType=ESYMBOLTYPE_NONE;
sTemp.mUnion=0;
}
}
generate_minus_operator=true;
expecting_value=false;
break;
}
case ESCRIPTTOKEN_KEYWORD_ALLARGS:
{
if (!expecting_value)
{
in_expression=false;
break;
}
++p_token;
if (p_args)
{
sTemp.mType=ESYMBOLTYPE_STRUCTURE;
sTemp.mpStructure=p_args;
sExpressionEvaluator.Input(&sTemp);
}
generate_minus_operator=true;
expecting_value=false;
break;
}
case ESCRIPTTOKEN_ADD:
case ESCRIPTTOKEN_MINUS:
case ESCRIPTTOKEN_MULTIPLY:
case ESCRIPTTOKEN_DIVIDE:
case ESCRIPTTOKEN_DOT:
case ESCRIPTTOKEN_OR:
case ESCRIPTTOKEN_AND:
case ESCRIPTTOKEN_LESSTHAN:
case ESCRIPTTOKEN_GREATERTHAN:
case ESCRIPTTOKEN_EQUALS:
case ESCRIPTTOKEN_STARTARRAY:
++p_token;
sExpressionEvaluator.Input((EScriptToken)token_value);
generate_minus_operator=false;
expecting_value=true;
if (token_value==ESCRIPTTOKEN_DOT)
{
operator_is_dot=true;
}
else
{
operator_is_dot=false;
}
break;
case ESCRIPTTOKEN_ENDARRAY:
case ESCRIPTTOKEN_ENDOFLINE:
++p_token;
break;
case ESCRIPTTOKEN_ENDOFLINENUMBER:
p_token+=5;
break;
case ESCRIPTTOKEN_ENDOFFILE:
case ESCRIPTTOKEN_KEYWORD_ENDSCRIPT:
case ESCRIPTTOKEN_COMMA:
Dbg_MsgAssert(0,("Unexpected token '%s' in expression, line %d of %s",GetTokenName((EScriptToken)token_value),GetLineNumber(p_token),GetSourceFile(p_token)));
break;
default:
// Unrecognized expression
Dbg_MsgAssert(0,("Don't know how to evaluate '%s', at line %d of %s",GetTokenName((EScriptToken)token_value),GetLineNumber(p_token),GetSourceFile(p_token)));
break;
}
// This cannot be commented in all the time, because often the evaluator will report
// errors when it gets called via the FindReferences function, which scans through scripts
// but does not execute them. Errors occur in this case when script parameters are used
// in expressions, because when FindReferences is scanning through the script it has no
// parameters. Need to fix FindReferences so that it can scan over expressions without
// executing them.
//if (sExpressionEvaluator.GetError())
//{
// Dbg_MsgAssert(0,("Evaluator error: File %s, line %d",GetSourceFile(p_token),GetLineNumber(p_token)));
//}
#ifdef __NOPT_ASSERT__
if (sExpressionEvaluator.ErrorCheckingEnabled() && sExpressionEvaluator.GetError())
{
printf("Evaluator error: File %s, line %d",GetSourceFile(p_token),GetLineNumber(p_token));
}
#endif
}
sExpressionEvaluator.GetResult(p_result);
#ifdef __NOPT_ASSERT__
if (sExpressionEvaluator.ErrorCheckingEnabled() && sExpressionEvaluator.GetError())
{
printf("Evaluator error: File %s, line %d\n",GetSourceFile(p_token),GetLineNumber(p_token));
}
#endif
return p_token;
}
// Parses the components in QB format pointed to by p_token, and adds them to p_dest until the
// end-of-line is reached.
// For example, p_token may point to: x=9 foo="Blaa"
// Used when creating the structure of function parameters, and the structure of default parameters
// for scripts.
// p_args contains the structure defining the value of any args referred to using the <,> operators.
// Returns a pointer to the next token after parsing the tokens pointed to by p_token.
//
// Note: This gets called for every line of script executed, hence is probably a bottleneck in script
// execution speed. Could speed up a lot by pre-generating any function parameters that are constant, ie
// contain no <,> operators.
uint8 *AddComponentsUntilEndOfLine(CStruct *p_dest, uint8 *p_token, CStruct *p_args)
{
Dbg_MsgAssert(p_dest,("NULL p_dest"));
Dbg_MsgAssert(p_token,("NULL p_token"));
while (true)
{
// Execute any random operators.
p_token=DoAnyRandomsOrJumps(p_token);
if (sIsEndOfLine(p_token))
{
break;
}
// This is so that function calls can be put in expressions, which are always enclosed
// in parentheses.
if (*p_token==ESCRIPTTOKEN_CLOSEPARENTH)
{
break;
}
switch (*p_token)
{
case ESCRIPTTOKEN_NAME:
{
uint8 *p_name_token=p_token;
++p_token;
uint32 name_checksum=Read4Bytes(p_token).mChecksum;
p_token+=4;
// Execute any random operators. This has to be done each time p_token is advanced.
p_token=DoAnyRandomsOrJumps(p_token);
if (*p_token==ESCRIPTTOKEN_EQUALS)
{
++p_token;
p_token=DoAnyRandomsOrJumps(p_token);
Dbg_MsgAssert(!sIsEndOfLine(p_token),("Syntax error, nothing following '=', File %s, line %d",GetSourceFile(p_token),GetLineNumber(p_token)));
if (*p_token==ESCRIPTTOKEN_OPENPARENTH)
{
CComponent *p_comp=new CComponent;
p_token=Evaluate(p_token,p_args,p_comp);
if (p_comp->mType!=ESYMBOLTYPE_NONE)
{
p_comp->mNameChecksum=name_checksum;
p_dest->AddComponent(p_comp);
}
else
{
delete p_comp;
}
}
else
{
p_token=sAddComponentFromQB(p_dest,name_checksum,p_token,p_args);
}
}
else
{
p_token=sAddComponentFromQB(p_dest,NO_NAME,p_name_token,p_args);
}
break;
}
case ESCRIPTTOKEN_KEYWORD_ALLARGS:
++p_token;
if (p_args)
{
*p_dest+=*p_args;
}
break;
case ESCRIPTTOKEN_STARTSTRUCT:
p_token=sAddComponentsWithinCurlyBraces(p_dest,p_token,p_args);
break;
case ESCRIPTTOKEN_ARG:
p_token=sAddComponentFromQB(p_dest,NO_NAME,p_token,p_args);
break;
case ESCRIPTTOKEN_ENDOFLINE:
case ESCRIPTTOKEN_ENDOFLINENUMBER:
break;
case ESCRIPTTOKEN_ENDOFFILE:
Dbg_MsgAssert(0,("End of file reached during AddComponentsUntilEndOfLine ?"));
break;
case ESCRIPTTOKEN_COMMA:
++p_token;
break;
default:
{
if (*p_token==ESCRIPTTOKEN_OPENPARENTH)
{
CComponent *p_comp=new CComponent;
p_token=Evaluate(p_token,p_args,p_comp);
if (p_comp->mType==ESYMBOLTYPE_STRUCTURE)
{
// The CStruct::AddComponent function does not allow unnamed structure
// components to be added, so merge in the contents of the structure instead.
p_dest->AppendStructure(p_comp->mpStructure);
// Now p_comp does have to be cleaned up and deleted, because it has not
// been given to p_dest.
CleanUpComponent(p_comp);
delete p_comp;
}
else
{
if (p_comp->mType!=ESYMBOLTYPE_NONE)
{
p_comp->mNameChecksum=NO_NAME;
p_dest->AddComponent(p_comp);
}
else
{
delete p_comp;
}
}
}
else
{
p_token=sAddComponentFromQB(p_dest,NO_NAME,p_token,p_args);
}
break;
}
}
}
// Note: Does not skip over the end-of-lines. This is so that if any asserts go off during the
// C-function or member function call that this structure is going to be used as parameters too,
// the assert will print the correct line number, rather than the one following.
return p_token;
}
// Deletes any entity pointed to by p_sym, then removes p_sym from the symbol table.
void CleanUpAndRemoveSymbol(CSymbolTableEntry *p_sym)
{
Dbg_MsgAssert(p_sym,("NULL p_sym"));
switch (p_sym->mType)
{
case ESYMBOLTYPE_INTEGER:
case ESYMBOLTYPE_FLOAT:
case ESYMBOLTYPE_NAME:
// Nothing to delete
break;
case ESYMBOLTYPE_STRING:
Dbg_MsgAssert(p_sym->mpString,("NULL p_sym->mpString"));
DeleteString(p_sym->mpString);
break;
case ESYMBOLTYPE_LOCALSTRING:
Dbg_MsgAssert(p_sym->mpLocalString,("NULL p_sym->mpLocalString"));
DeleteString(p_sym->mpLocalString);
break;
case ESYMBOLTYPE_PAIR:
Dbg_MsgAssert(p_sym->mpPair,("NULL p_sym->mpPair"));
delete p_sym->mpPair;
break;
case ESYMBOLTYPE_VECTOR:
Dbg_MsgAssert(p_sym->mpVector,("NULL p_sym->mpVector"));
delete p_sym->mpVector;
break;
case ESYMBOLTYPE_QSCRIPT:
#ifdef __PLAT_NGC__
NsARAM::free( p_sym->mScriptOffset );
#else
Dbg_MsgAssert(p_sym->mpScript,("NULL p_sym->mpScript"));
Mem::Free(p_sym->mpScript);
#endif // __PLAT_NGC__
break;
case ESYMBOLTYPE_STRUCTURE:
Dbg_MsgAssert(p_sym->mpStructure,("NULL p_sym->mpStructure"));
delete p_sym->mpStructure;
break;
case ESYMBOLTYPE_ARRAY:
Dbg_MsgAssert(p_sym->mpArray,("NULL p_sym->mpArray"));
CleanUpArray(p_sym->mpArray);
delete p_sym->mpArray;
break;
default:
Dbg_MsgAssert(0,("Bad type of '%s' in p_sym",GetTypeName(p_sym->mType)));
break;
}
p_sym->mUnion=0;
RemoveSymbol(p_sym);
}
// TODO: Remove? Currently used in cfuncs.cpp to delete the NodeArray, but now that we
// have the ability to unload qb files that should not be necessary any more.
// Note: Just found it is also needed in parkgen.cpp, so don't remove after all.
void CleanUpAndRemoveSymbol(uint32 checksum)
{
CSymbolTableEntry *p_sym=LookUpSymbol(checksum);
if (p_sym)
{
CleanUpAndRemoveSymbol(p_sym);
}
}
void CleanUpAndRemoveSymbol(const char *p_symbolName)
{
CleanUpAndRemoveSymbol(Crc::GenerateCRCFromString(p_symbolName));
}
#define HASHBITS 12
struct SChecksum
{
uint32 mChecksum;
char *mpName;
SChecksum *mpNext;
};
static SChecksum *sp_hash_table=NULL;
const char *GetChecksumNameFromLastQB(uint32 checksum)
{
Dbg_MsgAssert(sp_hash_table,("NULL sp_hash_table"));
SChecksum *p_entry=&sp_hash_table[checksum&((1<<HASHBITS)-1)];
while (p_entry)
{
if (p_entry->mChecksum==checksum)
{
return p_entry->mpName;
}
p_entry=p_entry->mpNext;
}
return NULL;
}
void RemoveChecksumNameLookupHashTable()
{
if (sp_hash_table)
{
SChecksum *p_entry=sp_hash_table;
for (uint32 i=0; i<(1<<HASHBITS); ++i)
{
SChecksum *p_ch=p_entry->mpNext;
while (p_ch)
{
SChecksum *p_next=p_ch->mpNext;
// The dynamically allocated entries must have had their mpName set.
Dbg_MsgAssert(p_ch->mpName,("NULL p_ch->mpName ?"));
Mem::Free(p_ch->mpName);
Mem::Free(p_ch);
p_ch=p_next;
}
// The entries in the contiguous array may not have their mpName set.
if (p_entry->mpName)
{
Mem::Free(p_entry->mpName);
}
++p_entry;
}
Mem::Free(sp_hash_table);
sp_hash_table=NULL;
}
}
uint8 *SkipToStartOfNextLine(uint8 *p_token)
{
int curly_bracket_count=0;
int square_bracket_count=0;
int parenth_count=0;
while (true)
{
if (*p_token==ESCRIPTTOKEN_STARTSTRUCT) ++curly_bracket_count;
if (*p_token==ESCRIPTTOKEN_ENDSTRUCT) --curly_bracket_count;
Dbg_MsgAssert(curly_bracket_count>=0,("Negative curly_bracket_count ??"));
if (*p_token==ESCRIPTTOKEN_STARTARRAY) ++square_bracket_count;
if (*p_token==ESCRIPTTOKEN_ENDARRAY) --square_bracket_count;
Dbg_MsgAssert(square_bracket_count>=0,("Negative square_bracket_count ??"));
if (*p_token==ESCRIPTTOKEN_OPENPARENTH) ++parenth_count;
if (*p_token==ESCRIPTTOKEN_CLOSEPARENTH) --parenth_count;
Dbg_MsgAssert(parenth_count>=0,("Negative parenth_count, File %s, line %d",GetSourceFile(p_token),GetLineNumber(p_token)));
if (curly_bracket_count==0 &&
square_bracket_count==0 &&
parenth_count==0 &&
sIsEndOfLine(p_token))
{
break;
}
p_token=SkipToken(p_token);
}
p_token=SkipToken(p_token);
return p_token;
}
// Parses a qb file, creating all the symbols (scripts, arrays etc) defined within.
// If the assertIfDuplicateSymbols is set, then it will assert if any of the symbols in the qb
// exist already.
// This flag is set when loading all the startup qb's, because we don't want clashing symbols.
// It is not set when reloading a qb during development though, since that involves reloading
// existing symbols.
// The file name is also passed so that each symbol knows what qb it came from, which allows
// all the symbols from a particular qb to be unloaded using the UnloadQB function (in file.cpp)
void ParseQB(const char *p_fileName, uint8 *p_qb, EBoolAssertIfDuplicateSymbols assertIfDuplicateSymbols, bool allocateChecksumNameLookupTable)
{
Dbg_MsgAssert(p_fileName,("NULL p_fileName"));
Dbg_MsgAssert(p_qb,("NULL p_qb"));
// Do a first parse through the qb to register the checksum names.
// They get added to a lookup table that can be queried using GetChecksumNameFromLastQB defined above.
// The lookup table only contains the checksum names defined in this qb.
// GetChecksumNameFromLastQB is currently only used by the game-specific nodearray.cpp to generate a lookup
// table of node name prefixes.
//
// If __NOPT_ASSERT__ is set the checksum names also get added to a big permanent lookup table so that
// the FindChecksumName function can be used at any time in asserts.
//
uint8 *p_token=p_qb;
bool end_of_file=false;
// Remove any existing checksum-name-lookup hash table.
RemoveChecksumNameLookupHashTable();
// K: Added the passed flag allocateChecksumNameLookupTable so that we can avoid allocating the table
// if we know it will not be needed. Gary needs this for the cutscenes where there is not enough memory
// to allocate the table.
// By default the flag is set to true however because the table is needed when generating prefix info
// for the nodearray, and we don't know whether the qb contains the nodearray until after finishing
// parsing it.
if (allocateChecksumNameLookupTable)
{
// Reallocate it.
bool use_debug_heap=false;
#ifdef __NOPT_ASSERT__
if (!assertIfDuplicateSymbols)
{
// They're doing a qbr ...
if (Mem::Manager::sHandle().GetHeap(0x70cb0238/*DebugHeap*/))
{
// The debug heap exists, so use it.
// Need to do this because there is often not enough memory otherwise.
use_debug_heap=true;
}
}
#endif
if (use_debug_heap)
{
Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().DebugHeap());
}
else
{
Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().ScriptHeap());
}
Dbg_MsgAssert(sp_hash_table==NULL,("sp_hash_table not NULL ?"));
sp_hash_table=(SChecksum*)Mem::Malloc((1<<HASHBITS)*sizeof(SChecksum));
Dbg_MsgAssert(sp_hash_table,("NULL sp_hash_table"));
Mem::Manager::sHandle().PopContext();
// Initialise it.
SChecksum *p_entry=sp_hash_table;
for (uint32 i=0; i<(1<<HASHBITS); ++i)
{
p_entry->mChecksum=0;
p_entry->mpName=NULL;
p_entry->mpNext=NULL;
++p_entry;
}
}
// Line number info in qb's is excluded when doing a release build to save memory.
// Need to stop with an error message if there is line number info when there should not
// be, otherwise the release build will run out of memory & it will be hard to track down
// why since there will be no asserts.
// This flag indicates whether to give the error message or not.
bool allow_line_number_info=false;
if (!assertIfDuplicateSymbols)
{
// Doing a qbr, so allow line number info, otherwise
// we won't be able to qbr when __NOPT_ASSERT__ is not defined.
allow_line_number_info=true;
}
#ifdef __NOPT_ASSERT__
// Allow line number info when asserts are on, cos the script heap will have an extra 200K for them.
allow_line_number_info=true;
#endif
#ifdef __PLAT_WN32__
// PC has lots of memory.
allow_line_number_info=true;
#endif
while (!end_of_file)
{
switch (*p_token)
{
case ESCRIPTTOKEN_CHECKSUM_NAME:
{
++p_token;
uint32 checksum=Read4Bytes(p_token).mChecksum;
const char *p_name=(const char*)(p_token+4);
if (allocateChecksumNameLookupTable)
{
// Add it to the table.
SChecksum *p_entry=&sp_hash_table[checksum&((1<<HASHBITS)-1)];
if (p_entry->mpName)
{
// The first slot is already occupied, so create a new SChecksum and
// link it in between the first and the rest.
SChecksum *p_new=(SChecksum*)Mem::Malloc(sizeof(SChecksum));
p_new->mChecksum=checksum;
p_new->mpName=(char*)Mem::Malloc(strlen(p_name)+1);
strcpy(p_new->mpName,p_name);
p_new->mpNext=p_entry->mpNext;
p_entry->mpNext=p_new;
}
else
{
// First slot is not occupied, so stick it in there.
p_entry->mChecksum=checksum;
p_entry->mpName=(char*)Mem::Malloc(strlen(p_name)+1);
strcpy(p_entry->mpName,p_name);
p_entry->mpNext=NULL;
}
}
// Add to the big lookup table of all checksum names, used by FindChecksumName
// Doing this here rather than the main parsing loop after this one so that the names
// are registered first in case any asserts go off.
AddChecksumName(checksum,p_name);
// Skip over the checksum.
p_token+=4;
// Skip over the name.
while (*p_token)
{
++p_token;
}
// Skip over the terminator.
++p_token;
break;
}
case ESCRIPTTOKEN_ENDOFFILE:
end_of_file=true;
break;
case ESCRIPTTOKEN_ENDOFLINENUMBER:
{
if (allow_line_number_info)
{
p_token=SkipToken(p_token);
}
else
{
printf("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n\n");
printf("Found line number info in '%s' !\nLine number info needs to be removed when __NOPT_ASSERT__ is not defined\nin order to save memory.\nTry doing a 'cleanass nolinenumbers' and running again.\n",p_fileName);
while (1);
}
break;
}
default:
p_token=SkipToken(p_token);
break;
}
}
p_token=p_qb;
end_of_file=false;
// That's all the checksum names added.
// Register the file name checksum so that FindChecksumName will be able to find it.
// (Required if a script assert goes off that needs to print the source file name)
AddChecksumName(Crc::GenerateCRCFromString(p_fileName),p_fileName);
// Store the checksum of the file name so that if an assert goes off during parsing the file name can be printed.
s_qb_being_parsed=Crc::GenerateCRCFromString(p_fileName);
// Make sure we're using the script heap, cos we're about to create a bunch of scripty stuff.
Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().ScriptHeap());
while (!end_of_file)
{
p_token=SkipEndOfLines(p_token);
switch (*p_token)
{
case ESCRIPTTOKEN_NAME:
{
p_token=sCreateSymbolOfTheFormNameEqualsValue(p_token,p_fileName,assertIfDuplicateSymbols);
break;
}
case ESCRIPTTOKEN_KEYWORD_SCRIPT:
{
// Get the new script's name checksum ...
++p_token; // Skip over the ESCRIPTTOKEN_KEYWORD_SCRIPT
Dbg_MsgAssert(*p_token==ESCRIPTTOKEN_NAME,("\nKeyword 'script' must be followed by a name, line %d of %s",GetLineNumber(p_token),p_fileName));
++p_token; // Skip over the ESCRIPTTOKEN_NAME
uint32 name_checksum=Read4Bytes(p_token).mChecksum;
p_token+=4; // Skip over the name checksum
// Calculate the length of the script data in bytes.
uint8 *p_script_data=p_token;
p_token=SkipOverScript(p_token);
uint32 script_data_size=p_token-p_script_data;
// Calculate a checksum of the contents of the new script.
// The purpose of the contents checksum is to prevent unnecessary restarting of CScript's
// when a qb is reloaded. It may be that only one script in the qb has changed, so we only
// want the CScript's that were referencing that script to restart.
// Note: The LoadQB function in gel\scripting\file.cpp does the restarting of existing
// CScripts by checking the mGotReloaded flag in the CSymbolTableEntry for the script.
uint32 new_contents_checksum=CalculateScriptContentsChecksum(p_script_data);
// Check to see if a script with this name exists already.
CSymbolTableEntry *p_existing_entry=LookUpSymbol(name_checksum);
if (p_existing_entry)
{
if (assertIfDuplicateSymbols)
{
Dbg_MsgAssert(0,("The symbol '%s' is defined twice, in %s and %s",FindChecksumName(name_checksum),FindChecksumName(p_existing_entry->mSourceFileNameChecksum),p_fileName));
}
if (p_existing_entry->mType==ESYMBOLTYPE_QSCRIPT)
{
// A script with the same name does already exist.
// Compare its contents checksum with the new one.
#ifdef __PLAT_NGC__
uint32 header[(SCRIPT_HEADER_SIZE/4)];
NsDMA::toMRAM( header, p_existing_entry->mScriptOffset, SCRIPT_HEADER_SIZE );
uint32 old_contents_checksum=header[0];
#else
Dbg_MsgAssert(p_existing_entry->mpScript,("NULL p_existing_entry->mpScript ??"));
uint32 old_contents_checksum=*(uint32*)(p_existing_entry->mpScript);
#endif // __PLAT_NGC__
if (new_contents_checksum != old_contents_checksum)
{
// The contents of the new script differ from the old!
// So remove the old one, and create the new.
CleanUpAndRemoveSymbol(p_existing_entry);
sCreateScriptSymbol(name_checksum,
new_contents_checksum,
p_script_data,
script_data_size,
p_fileName);
}
else
{
// The contents of the script have not changed!
// So there's nothing to do.
}
}
else
{
// Remove the existing symbol, whatever it is. (It isn't a script)
CleanUpAndRemoveSymbol(p_existing_entry);
// Create the new script symbol.
sCreateScriptSymbol(name_checksum,new_contents_checksum,p_script_data,script_data_size,p_fileName);
}
}
else
{
// No symbol currently exists with this name, so create the new script symbol.
sCreateScriptSymbol(name_checksum,new_contents_checksum,p_script_data,script_data_size,p_fileName);
}
break;
}
case ESCRIPTTOKEN_CHECKSUM_NAME:
// These have been done already, so skip over.
p_token=SkipToken(p_token);
break;
case ESCRIPTTOKEN_ENDOFFILE:
end_of_file=true;
break;
default:
Dbg_MsgAssert(0,("\nConfused by line %d of %s.\nExpected either a script definition (Script ... EndScript)\nor a constant definition (Something=...)",GetLineNumber(p_token),p_fileName));
break;
}
}
Mem::Manager::sHandle().PopContext();
s_qb_being_parsed=0;
}
// Used by the EditorCameraComponent when it checks polys to see if they are Kill polys
bool ScriptContainsName(uint8 *p_script, uint32 searchName)
{
Dbg_MsgAssert(p_script,("NULL p_script"));
//int num_names_found=0;
//uint32 last_name=0;
uint8 *p_token=p_script;
while (*p_token!=ESCRIPTTOKEN_KEYWORD_ENDSCRIPT)
{
switch (*p_token)
{
case ESCRIPTTOKEN_NAME:
{
++p_token;
uint32 name=Read4Bytes(p_token).mChecksum;
p_token+=4;
if (name == searchName)
{
return true;
}
//++num_names_found;
//last_name=name;
break;
}
default:
p_token=SkipToken(p_token);
break;
}
}
/*
// I put this in so that scripts that consist of a single call to another script will have that
// scanned. This was to fix a bug where the kill poly over the swimming pool in Hawaii was not
// being detected. However, this fix then meant that the kill polys around the hotel would get
// detected, meaning that the cursor could never be got off the hotel. So commented it out
// for the moment, since at least the first bug does not make the cursor get stuck.
if (num_names_found==1)
{
Script::CScriptCache *p_script_cache=Script::CScriptCache::Instance();
Dbg_MsgAssert(p_script_cache,("NULL p_script_cache"));
p_script=p_script_cache->GetScript(last_name);
if (p_script)
{
bool contains_name=ScriptContainsName(p_script, searchName);
p_script_cache->DecrementScriptUsage(last_name);
return contains_name;
}
}
*/
return false;
}
// Used by the EditorCameraComponent when it checks polys to see if they are Kill polys
bool ScriptContainsAnyOfTheNames(uint32 scriptName, uint32 *p_names, int numNames)
{
Script::CScriptCache *p_script_cache=Script::CScriptCache::Instance();
Dbg_MsgAssert(p_script_cache,("NULL p_script_cache"));
uint8 *p_script=p_script_cache->GetScript(scriptName);
Dbg_MsgAssert(p_script,("NULL p_script for %s",Script::FindChecksumName(scriptName)));
bool contains_name=false;
Dbg_MsgAssert(p_names,("NULL p_names"));
uint32 *p_name=p_names;
for (int i=0; i<numNames; ++i)
{
if (ScriptContainsName(p_script,*p_name))
{
//printf("Script %s contains %s\n",Script::FindChecksumName(scriptName),Script::FindChecksumName(*p_name));
contains_name=true;
break;
}
++p_name;
}
p_script_cache->DecrementScriptUsage(scriptName);
return contains_name;
}
} // namespace Script