mirror of
https://github.com/TorqueGameEngines/Torque3D.git
synced 2026-01-20 04:34:48 +00:00
Conflicts: Engine/source/app/net/net.cpp Engine/source/console/astNodes.cpp Engine/source/console/compiledEval.cpp Engine/source/console/console.h Engine/source/console/consoleInternal.h Engine/source/console/engineAPI.h
2245 lines
73 KiB
C++
2245 lines
73 KiB
C++
//-----------------------------------------------------------------------------
|
|
// Copyright (c) 2012 GarageGames, LLC
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
// of this software and associated documentation files (the "Software"), to
|
|
// deal in the Software without restriction, including without limitation the
|
|
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
|
// sell copies of the Software, and to permit persons to whom the Software is
|
|
// furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in
|
|
// all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
// IN THE SOFTWARE.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
#include "platform/platform.h"
|
|
#include "console/console.h"
|
|
|
|
#include "console/ast.h"
|
|
#include "core/tAlgorithm.h"
|
|
|
|
#include "core/strings/findMatch.h"
|
|
#include "core/strings/stringUnit.h"
|
|
#include "console/consoleInternal.h"
|
|
#include "core/stream/fileStream.h"
|
|
#include "console/compiler.h"
|
|
|
|
#include "console/simBase.h"
|
|
#include "console/telnetDebugger.h"
|
|
#include "sim/netStringTable.h"
|
|
#include "console/ICallMethod.h"
|
|
#include "console/stringStack.h"
|
|
#include "util/messaging/message.h"
|
|
#include "core/frameAllocator.h"
|
|
|
|
#ifndef TORQUE_TGB_ONLY
|
|
#include "materials/materialDefinition.h"
|
|
#include "materials/materialManager.h"
|
|
#endif
|
|
|
|
// Uncomment to optimize function calls at the expense of potential invalid package lookups
|
|
//#define COMPILER_OPTIMIZE_FUNCTION_CALLS
|
|
|
|
using namespace Compiler;
|
|
|
|
enum EvalConstants {
|
|
MaxStackSize = 1024,
|
|
MethodOnComponent = -2
|
|
};
|
|
|
|
namespace Con
|
|
{
|
|
// Current script file name and root, these are registered as
|
|
// console variables.
|
|
extern StringTableEntry gCurrentFile;
|
|
extern StringTableEntry gCurrentRoot;
|
|
extern S32 gObjectCopyFailures;
|
|
}
|
|
|
|
/// Frame data for a foreach/foreach$ loop.
|
|
struct IterStackRecord
|
|
{
|
|
/// If true, this is a foreach$ loop; if not, it's a foreach loop.
|
|
bool mIsStringIter;
|
|
|
|
/// The iterator variable.
|
|
Dictionary::Entry* mVariable;
|
|
|
|
/// Information for an object iterator loop.
|
|
struct ObjectPos
|
|
{
|
|
/// The set being iterated over.
|
|
SimSet* mSet;
|
|
|
|
/// Current index in the set.
|
|
U32 mIndex;
|
|
};
|
|
|
|
/// Information for a string iterator loop.
|
|
struct StringPos
|
|
{
|
|
/// The raw string data on the string stack.
|
|
const char* mString;
|
|
|
|
/// Current parsing position.
|
|
U32 mIndex;
|
|
};
|
|
|
|
union
|
|
{
|
|
ObjectPos mObj;
|
|
StringPos mStr;
|
|
} mData;
|
|
};
|
|
|
|
IterStackRecord iterStack[ MaxStackSize ];
|
|
|
|
F64 floatStack[MaxStackSize];
|
|
S64 intStack[MaxStackSize];
|
|
|
|
|
|
|
|
|
|
StringStack STR;
|
|
ConsoleValueStack CSTK;
|
|
|
|
U32 _FLT = 0; ///< Stack pointer for floatStack.
|
|
U32 _UINT = 0; ///< Stack pointer for intStack.
|
|
U32 _ITER = 0; ///< Stack pointer for iterStack.
|
|
|
|
namespace Con
|
|
{
|
|
const char *getNamespaceList(Namespace *ns)
|
|
{
|
|
U32 size = 1;
|
|
Namespace * walk;
|
|
for(walk = ns; walk; walk = walk->mParent)
|
|
size += dStrlen(walk->mName) + 4;
|
|
char *ret = Con::getReturnBuffer(size);
|
|
ret[0] = 0;
|
|
for(walk = ns; walk; walk = walk->mParent)
|
|
{
|
|
dStrcat(ret, walk->mName);
|
|
if(walk->mParent)
|
|
dStrcat(ret, " -> ");
|
|
}
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------
|
|
|
|
F64 consoleStringToNumber(const char *str, StringTableEntry file, U32 line)
|
|
{
|
|
F64 val = dAtof(str);
|
|
if(val != 0)
|
|
return val;
|
|
else if(!dStricmp(str, "true"))
|
|
return 1;
|
|
else if(!dStricmp(str, "false"))
|
|
return 0;
|
|
else if(file)
|
|
{
|
|
Con::warnf(ConsoleLogEntry::General, "%s (%d): string always evaluates to 0.", file, line);
|
|
return 0;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
//------------------------------------------------------------
|
|
|
|
namespace Con
|
|
{
|
|
|
|
char *getReturnBuffer(U32 bufferSize)
|
|
|
|
{
|
|
return STR.getReturnBuffer(bufferSize);
|
|
}
|
|
|
|
char *getReturnBuffer( const char *stringToCopy )
|
|
{
|
|
U32 len = dStrlen( stringToCopy ) + 1;
|
|
char *ret = STR.getReturnBuffer( len);
|
|
dMemcpy( ret, stringToCopy, len );
|
|
return ret;
|
|
}
|
|
|
|
char* getReturnBuffer( const String& str )
|
|
{
|
|
const U32 size = str.size();
|
|
char* ret = STR.getReturnBuffer( size );
|
|
dMemcpy( ret, str.c_str(), size );
|
|
return ret;
|
|
}
|
|
|
|
char* getReturnBuffer( const StringBuilder& str )
|
|
{
|
|
char* buffer = Con::getReturnBuffer( str.length() + 1 );
|
|
str.copy( buffer );
|
|
buffer[ str.length() ] = '\0';
|
|
|
|
return buffer;
|
|
}
|
|
|
|
char *getArgBuffer(U32 bufferSize)
|
|
{
|
|
return STR.getArgBuffer(bufferSize);
|
|
}
|
|
|
|
ConsoleValueRef getFloatArg(F64 arg)
|
|
{
|
|
ConsoleValueRef ref = arg;
|
|
return ref;
|
|
}
|
|
|
|
ConsoleValueRef getIntArg(S32 arg)
|
|
{
|
|
ConsoleValueRef ref = arg;
|
|
return ref;
|
|
}
|
|
|
|
char *getStringArg( const char *arg )
|
|
{
|
|
U32 len = dStrlen( arg ) + 1;
|
|
char *ret = STR.getArgBuffer( len );
|
|
dMemcpy( ret, arg, len );
|
|
return ret;
|
|
}
|
|
|
|
char* getStringArg( const String& arg )
|
|
{
|
|
const U32 size = arg.size();
|
|
char* ret = STR.getArgBuffer( size );
|
|
dMemcpy( ret, arg.c_str(), size );
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------
|
|
|
|
inline void ExprEvalState::setCurVarName(StringTableEntry name)
|
|
{
|
|
if(name[0] == '$')
|
|
currentVariable = globalVars.lookup(name);
|
|
else if( getStackDepth() > 0 )
|
|
currentVariable = getCurrentFrame().lookup(name);
|
|
if(!currentVariable && gWarnUndefinedScriptVariables)
|
|
Con::warnf(ConsoleLogEntry::Script, "Variable referenced before assignment: %s", name);
|
|
}
|
|
|
|
inline void ExprEvalState::setCurVarNameCreate(StringTableEntry name)
|
|
{
|
|
if(name[0] == '$')
|
|
currentVariable = globalVars.add(name);
|
|
else if( getStackDepth() > 0 )
|
|
currentVariable = getCurrentFrame().add(name);
|
|
else
|
|
{
|
|
currentVariable = NULL;
|
|
Con::warnf(ConsoleLogEntry::Script, "Accessing local variable in global scope... failed: %s", name);
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------
|
|
|
|
inline S32 ExprEvalState::getIntVariable()
|
|
{
|
|
return currentVariable ? currentVariable->getIntValue() : 0;
|
|
}
|
|
|
|
inline F64 ExprEvalState::getFloatVariable()
|
|
{
|
|
return currentVariable ? currentVariable->getFloatValue() : 0;
|
|
}
|
|
|
|
inline const char *ExprEvalState::getStringVariable()
|
|
{
|
|
return currentVariable ? currentVariable->getStringValue() : "";
|
|
}
|
|
|
|
//------------------------------------------------------------
|
|
|
|
inline void ExprEvalState::setIntVariable(S32 val)
|
|
{
|
|
AssertFatal(currentVariable != NULL, "Invalid evaluator state - trying to set null variable!");
|
|
currentVariable->setIntValue(val);
|
|
}
|
|
|
|
inline void ExprEvalState::setFloatVariable(F64 val)
|
|
{
|
|
AssertFatal(currentVariable != NULL, "Invalid evaluator state - trying to set null variable!");
|
|
currentVariable->setFloatValue(val);
|
|
}
|
|
|
|
inline void ExprEvalState::setStringVariable(const char *val)
|
|
{
|
|
AssertFatal(currentVariable != NULL, "Invalid evaluator state - trying to set null variable!");
|
|
currentVariable->setStringValue(val);
|
|
}
|
|
|
|
inline void ExprEvalState::setCopyVariable()
|
|
{
|
|
if (copyVariable)
|
|
{
|
|
switch (copyVariable->value.type)
|
|
{
|
|
case ConsoleValue::TypeInternalInt:
|
|
currentVariable->setIntValue(copyVariable->getIntValue());
|
|
break;
|
|
case ConsoleValue::TypeInternalFloat:
|
|
currentVariable->setFloatValue(copyVariable->getFloatValue());
|
|
break;
|
|
default:
|
|
currentVariable->setStringValue(copyVariable->getStringValue());
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------
|
|
|
|
// Gets a component of an object's field value or a variable and returns it
|
|
// in val.
|
|
static void getFieldComponent( SimObject* object, StringTableEntry field, const char* array, StringTableEntry subField, char val[] )
|
|
{
|
|
const char* prevVal = NULL;
|
|
|
|
// Grab value from object.
|
|
if( object && field )
|
|
prevVal = object->getDataField( field, array );
|
|
|
|
// Otherwise, grab from the string stack. The value coming in will always
|
|
// be a string because that is how multicomponent variables are handled.
|
|
else
|
|
prevVal = STR.getStringValue();
|
|
|
|
// Make sure we got a value.
|
|
if ( prevVal && *prevVal )
|
|
{
|
|
static const StringTableEntry xyzw[] =
|
|
{
|
|
StringTable->insert( "x" ),
|
|
StringTable->insert( "y" ),
|
|
StringTable->insert( "z" ),
|
|
StringTable->insert( "w" )
|
|
};
|
|
|
|
static const StringTableEntry rgba[] =
|
|
{
|
|
StringTable->insert( "r" ),
|
|
StringTable->insert( "g" ),
|
|
StringTable->insert( "b" ),
|
|
StringTable->insert( "a" )
|
|
};
|
|
|
|
// Translate xyzw and rgba into the indexed component
|
|
// of the variable or field.
|
|
if ( subField == xyzw[0] || subField == rgba[0] )
|
|
dStrcpy( val, StringUnit::getUnit( prevVal, 0, " \t\n") );
|
|
|
|
else if ( subField == xyzw[1] || subField == rgba[1] )
|
|
dStrcpy( val, StringUnit::getUnit( prevVal, 1, " \t\n") );
|
|
|
|
else if ( subField == xyzw[2] || subField == rgba[2] )
|
|
dStrcpy( val, StringUnit::getUnit( prevVal, 2, " \t\n") );
|
|
|
|
else if ( subField == xyzw[3] || subField == rgba[3] )
|
|
dStrcpy( val, StringUnit::getUnit( prevVal, 3, " \t\n") );
|
|
|
|
else
|
|
val[0] = 0;
|
|
}
|
|
else
|
|
val[0] = 0;
|
|
}
|
|
|
|
// Sets a component of an object's field value based on the sub field. 'x' will
|
|
// set the first field, 'y' the second, and 'z' the third.
|
|
static void setFieldComponent( SimObject* object, StringTableEntry field, const char* array, StringTableEntry subField )
|
|
{
|
|
// Copy the current string value
|
|
char strValue[1024];
|
|
dStrncpy( strValue, STR.getStringValue(), 1024 );
|
|
|
|
char val[1024] = "";
|
|
const char* prevVal = NULL;
|
|
|
|
// Set the value on an object field.
|
|
if( object && field )
|
|
prevVal = object->getDataField( field, array );
|
|
|
|
// Set the value on a variable.
|
|
else if( gEvalState.currentVariable )
|
|
prevVal = gEvalState.getStringVariable();
|
|
|
|
// Ensure that the variable has a value
|
|
if (!prevVal)
|
|
return;
|
|
|
|
static const StringTableEntry xyzw[] =
|
|
{
|
|
StringTable->insert( "x" ),
|
|
StringTable->insert( "y" ),
|
|
StringTable->insert( "z" ),
|
|
StringTable->insert( "w" )
|
|
};
|
|
|
|
static const StringTableEntry rgba[] =
|
|
{
|
|
StringTable->insert( "r" ),
|
|
StringTable->insert( "g" ),
|
|
StringTable->insert( "b" ),
|
|
StringTable->insert( "a" )
|
|
};
|
|
|
|
// Insert the value into the specified
|
|
// component of the string.
|
|
if ( subField == xyzw[0] || subField == rgba[0] )
|
|
dStrcpy( val, StringUnit::setUnit( prevVal, 0, strValue, " \t\n") );
|
|
|
|
else if ( subField == xyzw[1] || subField == rgba[1] )
|
|
dStrcpy( val, StringUnit::setUnit( prevVal, 1, strValue, " \t\n") );
|
|
|
|
else if ( subField == xyzw[2] || subField == rgba[2] )
|
|
dStrcpy( val, StringUnit::setUnit( prevVal, 2, strValue, " \t\n") );
|
|
|
|
else if ( subField == xyzw[3] || subField == rgba[3] )
|
|
dStrcpy( val, StringUnit::setUnit( prevVal, 3, strValue, " \t\n") );
|
|
|
|
if ( val[0] != 0 )
|
|
{
|
|
// Update the field or variable.
|
|
if( object && field )
|
|
object->setDataField( field, 0, val );
|
|
else if( gEvalState.currentVariable )
|
|
gEvalState.setStringVariable( val );
|
|
}
|
|
}
|
|
|
|
ConsoleValueRef CodeBlock::exec(U32 ip, const char *functionName, Namespace *thisNamespace, U32 argc, ConsoleValueRef *argv, bool noCalls, StringTableEntry packageName, S32 setFrame)
|
|
{
|
|
#ifdef TORQUE_DEBUG
|
|
U32 stackStart = STR.mStartStackSize;
|
|
U32 consoleStackStart = CSTK.mStackPos;
|
|
#endif
|
|
|
|
static char traceBuffer[1024];
|
|
S32 i;
|
|
|
|
U32 iterDepth = 0;
|
|
|
|
incRefCount();
|
|
F64 *curFloatTable;
|
|
char *curStringTable;
|
|
S32 curStringTableLen = 0; //clint to ensure we dont overwrite it
|
|
STR.clearFunctionOffset();
|
|
StringTableEntry thisFunctionName = NULL;
|
|
bool popFrame = false;
|
|
if(argv)
|
|
{
|
|
// assume this points into a function decl:
|
|
U32 fnArgc = code[ip + 2 + 6];
|
|
thisFunctionName = CodeToSTE(code, ip);
|
|
S32 wantedArgc = getMin(argc-1, fnArgc); // argv[0] is func name
|
|
if(gEvalState.traceOn)
|
|
{
|
|
traceBuffer[0] = 0;
|
|
dStrcat(traceBuffer, "Entering ");
|
|
if(packageName)
|
|
{
|
|
dStrcat(traceBuffer, "[");
|
|
dStrcat(traceBuffer, packageName);
|
|
dStrcat(traceBuffer, "]");
|
|
}
|
|
if(thisNamespace && thisNamespace->mName)
|
|
{
|
|
dSprintf(traceBuffer + dStrlen(traceBuffer), sizeof(traceBuffer) - dStrlen(traceBuffer),
|
|
"%s::%s(", thisNamespace->mName, thisFunctionName);
|
|
}
|
|
else
|
|
{
|
|
dSprintf(traceBuffer + dStrlen(traceBuffer), sizeof(traceBuffer) - dStrlen(traceBuffer),
|
|
"%s(", thisFunctionName);
|
|
}
|
|
for(i = 0; i < argc; i++)
|
|
{
|
|
dStrcat(traceBuffer, argv[i+1]);
|
|
if(i != argc - 1)
|
|
dStrcat(traceBuffer, ", ");
|
|
}
|
|
dStrcat(traceBuffer, ")");
|
|
Con::printf("%s", traceBuffer);
|
|
}
|
|
gEvalState.pushFrame(thisFunctionName, thisNamespace);
|
|
popFrame = true;
|
|
|
|
for(i = 0; i < wantedArgc; i++)
|
|
{
|
|
StringTableEntry var = CodeToSTE(code, ip + (2 + 6 + 1) + (i * 2));
|
|
gEvalState.setCurVarNameCreate(var);
|
|
|
|
ConsoleValueRef ref = argv[i+1];
|
|
|
|
if (argv[i+1].isString())
|
|
gEvalState.setStringVariable(argv[i+1]);
|
|
else if (argv[i+1].isInt())
|
|
gEvalState.setIntVariable(argv[i+1]);
|
|
else if (argv[i+1].isFloat())
|
|
gEvalState.setFloatVariable(argv[i+1]);
|
|
else
|
|
gEvalState.setStringVariable(argv[i+1]);
|
|
}
|
|
ip = ip + (fnArgc * 2) + (2 + 6 + 1);
|
|
curFloatTable = functionFloats;
|
|
curStringTable = functionStrings;
|
|
curStringTableLen = functionStringsMaxLen;
|
|
}
|
|
else
|
|
{
|
|
curFloatTable = globalFloats;
|
|
curStringTable = globalStrings;
|
|
curStringTableLen = globalStringsMaxLen;
|
|
|
|
// If requested stack frame isn't available, request a new one
|
|
// (this prevents assert failures when creating local
|
|
// variables without a stack frame)
|
|
if (gEvalState.getStackDepth() <= setFrame)
|
|
setFrame = -1;
|
|
|
|
// Do we want this code to execute using a new stack frame?
|
|
if (setFrame < 0)
|
|
{
|
|
gEvalState.pushFrame(NULL, NULL);
|
|
popFrame = true;
|
|
}
|
|
else
|
|
{
|
|
// We want to copy a reference to an existing stack frame
|
|
// on to the top of the stack. Any change that occurs to
|
|
// the locals during this new frame will also occur in the
|
|
// original frame.
|
|
S32 stackIndex = gEvalState.getStackDepth() - setFrame - 1;
|
|
gEvalState.pushFrameRef( stackIndex );
|
|
popFrame = true;
|
|
}
|
|
}
|
|
|
|
// Reset the console stack frame which at this point will contain
|
|
// either nothing or argv[] which we just copied
|
|
CSTK.resetFrame();
|
|
|
|
// Grab the state of the telenet debugger here once
|
|
// so that the push and pop frames are always balanced.
|
|
const bool telDebuggerOn = TelDebugger && TelDebugger->isConnected();
|
|
if ( telDebuggerOn && setFrame < 0 )
|
|
TelDebugger->pushStackFrame();
|
|
|
|
StringTableEntry var, objParent;
|
|
StringTableEntry fnName;
|
|
StringTableEntry fnNamespace, fnPackage;
|
|
|
|
// Add local object creation stack [7/9/2007 Black]
|
|
static const U32 objectCreationStackSize = 32;
|
|
U32 objectCreationStackIndex = 0;
|
|
struct {
|
|
SimObject *newObject;
|
|
U32 failJump;
|
|
} objectCreationStack[ objectCreationStackSize ];
|
|
|
|
SimObject *currentNewObject = 0;
|
|
U32 failJump = 0;
|
|
StringTableEntry prevField = NULL;
|
|
StringTableEntry curField = NULL;
|
|
SimObject *prevObject = NULL;
|
|
SimObject *curObject = NULL;
|
|
SimObject *saveObject=NULL;
|
|
Namespace::Entry *nsEntry;
|
|
Namespace *ns;
|
|
const char* curFNDocBlock = NULL;
|
|
const char* curNSDocBlock = NULL;
|
|
const S32 nsDocLength = 128;
|
|
char nsDocBlockClass[nsDocLength];
|
|
|
|
U32 callArgc;
|
|
ConsoleValueRef *callArgv;
|
|
|
|
static char curFieldArray[256];
|
|
static char prevFieldArray[256];
|
|
|
|
CodeBlock *saveCodeBlock = smCurrentCodeBlock;
|
|
smCurrentCodeBlock = this;
|
|
if(this->name)
|
|
{
|
|
Con::gCurrentFile = this->name;
|
|
Con::gCurrentRoot = this->modPath;
|
|
}
|
|
const char * val;
|
|
const char *retValue;
|
|
|
|
// note: anything returned is pushed to CSTK and will be invalidated on the next exec()
|
|
ConsoleValueRef returnValue;
|
|
|
|
// The frame temp is used by the variable accessor ops (OP_SAVEFIELD_* and
|
|
// OP_LOADFIELD_*) to store temporary values for the fields.
|
|
static S32 VAL_BUFFER_SIZE = 1024;
|
|
FrameTemp<char> valBuffer( VAL_BUFFER_SIZE );
|
|
|
|
for(;;)
|
|
{
|
|
U32 instruction = code[ip++];
|
|
nsEntry = NULL;
|
|
breakContinue:
|
|
switch(instruction)
|
|
{
|
|
case OP_FUNC_DECL:
|
|
if(!noCalls)
|
|
{
|
|
fnName = CodeToSTE(code, ip);
|
|
fnNamespace = CodeToSTE(code, ip+2);
|
|
fnPackage = CodeToSTE(code, ip+4);
|
|
bool hasBody = ( code[ ip + 6 ] & 0x01 ) != 0;
|
|
U32 lineNumber = code[ ip + 6 ] >> 1;
|
|
|
|
Namespace::unlinkPackages();
|
|
ns = Namespace::find(fnNamespace, fnPackage);
|
|
ns->addFunction(fnName, this, hasBody ? ip : 0, curFNDocBlock ? dStrdup( curFNDocBlock ) : NULL, lineNumber );// if no body, set the IP to 0
|
|
if( curNSDocBlock )
|
|
{
|
|
if( fnNamespace == StringTable->lookup( nsDocBlockClass ) )
|
|
{
|
|
char *usageStr = dStrdup( curNSDocBlock );
|
|
usageStr[dStrlen(usageStr)] = '\0';
|
|
ns->mUsage = usageStr;
|
|
ns->mCleanUpUsage = true;
|
|
curNSDocBlock = NULL;
|
|
}
|
|
}
|
|
Namespace::relinkPackages();
|
|
|
|
// If we had a docblock, it's definitely not valid anymore, so clear it out.
|
|
curFNDocBlock = NULL;
|
|
|
|
//Con::printf("Adding function %s::%s (%d)", fnNamespace, fnName, ip);
|
|
}
|
|
ip = code[ip + 7];
|
|
break;
|
|
|
|
case OP_CREATE_OBJECT:
|
|
{
|
|
// Read some useful info.
|
|
objParent = CodeToSTE(code, ip);
|
|
bool isDataBlock = code[ip + 2];
|
|
bool isInternal = code[ip + 3];
|
|
bool isSingleton = code[ip + 4];
|
|
U32 lineNumber = code[ip + 5];
|
|
failJump = code[ip + 6];
|
|
|
|
// If we don't allow calls, we certainly don't allow creating objects!
|
|
// Moved this to after failJump is set. Engine was crashing when
|
|
// noCalls = true and an object was being created at the beginning of
|
|
// a file. ADL.
|
|
if(noCalls)
|
|
{
|
|
ip = failJump;
|
|
break;
|
|
}
|
|
|
|
// Push the old info to the stack
|
|
//Assert( objectCreationStackIndex < objectCreationStackSize );
|
|
objectCreationStack[ objectCreationStackIndex ].newObject = currentNewObject;
|
|
objectCreationStack[ objectCreationStackIndex++ ].failJump = failJump;
|
|
|
|
// Get the constructor information off the stack.
|
|
CSTK.getArgcArgv(NULL, &callArgc, &callArgv);
|
|
const char *objectName = callArgv[ 2 ];
|
|
|
|
// Con::printf("Creating object...");
|
|
|
|
// objectName = argv[1]...
|
|
currentNewObject = NULL;
|
|
|
|
// Are we creating a datablock? If so, deal with case where we override
|
|
// an old one.
|
|
if(isDataBlock)
|
|
{
|
|
// Con::printf(" - is a datablock");
|
|
|
|
// Find the old one if any.
|
|
SimObject *db = Sim::getDataBlockGroup()->findObject( objectName );
|
|
|
|
// Make sure we're not changing types on ourselves...
|
|
if(db && dStricmp(db->getClassName(), callArgv[1]))
|
|
{
|
|
Con::errorf(ConsoleLogEntry::General, "%s: Cannot re-declare data block %s with a different class.", getFileLine(ip), objectName);
|
|
ip = failJump;
|
|
STR.popFrame();
|
|
CSTK.popFrame();
|
|
break;
|
|
}
|
|
|
|
// If there was one, set the currentNewObject and move on.
|
|
if(db)
|
|
currentNewObject = db;
|
|
}
|
|
else if (!isInternal)
|
|
{
|
|
// IF we aren't looking at a local/internal object, then check if
|
|
// this object already exists in the global space
|
|
|
|
AbstractClassRep* rep = AbstractClassRep::findClassRep( objectName );
|
|
if (rep != NULL) {
|
|
Con::errorf(ConsoleLogEntry::General, "%s: Cannot name object [%s] the same name as a script class.",
|
|
getFileLine(ip), objectName);
|
|
ip = failJump;
|
|
STR.popFrame();
|
|
break;
|
|
}
|
|
|
|
SimObject *obj = Sim::findObject( (const char*)objectName );
|
|
if (obj /*&& !obj->isLocalName()*/)
|
|
{
|
|
if ( isSingleton )
|
|
{
|
|
// Make sure we're not trying to change types
|
|
if ( dStricmp( obj->getClassName(), (const char*)callArgv[1] ) != 0 )
|
|
{
|
|
Con::errorf(ConsoleLogEntry::General, "%s: Cannot re-declare object [%s] with a different class [%s] - was [%s].",
|
|
getFileLine(ip), objectName, (const char*)callArgv[1], obj->getClassName());
|
|
ip = failJump;
|
|
STR.popFrame();
|
|
CSTK.popFrame();
|
|
break;
|
|
}
|
|
|
|
// We're creating a singleton, so use the found object
|
|
// instead of creating a new object.
|
|
currentNewObject = obj;
|
|
}
|
|
else
|
|
{
|
|
const char* redefineBehavior = Con::getVariable( "$Con::redefineBehavior" );
|
|
|
|
if( dStricmp( redefineBehavior, "replaceExisting" ) == 0 )
|
|
{
|
|
// Save our constructor args as the argv vector is stored on the
|
|
// string stack and may get stomped if deleteObject triggers
|
|
// script execution.
|
|
|
|
ConsoleValueRef savedArgv[ StringStack::MaxArgs ];
|
|
for (int i=0; i<callArgc; i++) {
|
|
savedArgv[i] = callArgv[i];
|
|
}
|
|
//dMemcpy( savedArgv, callArgv, sizeof( savedArgv[ 0 ] ) * callArgc );
|
|
|
|
// Prevent stack value corruption
|
|
CSTK.pushFrame();
|
|
STR.pushFrame();
|
|
// --
|
|
|
|
obj->deleteObject();
|
|
obj = NULL;
|
|
|
|
// Prevent stack value corruption
|
|
CSTK.popFrame();
|
|
STR.popFrame();
|
|
// --
|
|
|
|
//dMemcpy( callArgv, savedArgv, sizeof( callArgv[ 0 ] ) * callArgc );
|
|
for (int i=0; i<callArgc; i++) {
|
|
callArgv[i] = savedArgv[i];
|
|
}
|
|
}
|
|
else if( dStricmp( redefineBehavior, "renameNew" ) == 0 )
|
|
{
|
|
for( U32 i = 1;; ++ i )
|
|
{
|
|
String newName = String::ToString( "%s%i", objectName, i );
|
|
if( !Sim::findObject( newName ) )
|
|
{
|
|
objectName = StringTable->insert( newName );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else if( dStricmp( redefineBehavior, "unnameNew" ) == 0 )
|
|
{
|
|
objectName = StringTable->insert( "" );
|
|
}
|
|
else if( dStricmp( redefineBehavior, "postfixNew" ) == 0 )
|
|
{
|
|
const char* postfix = Con::getVariable( "$Con::redefineBehaviorPostfix" );
|
|
String newName = String::ToString( "%s%s", objectName, postfix );
|
|
|
|
if( Sim::findObject( newName ) )
|
|
{
|
|
Con::errorf( ConsoleLogEntry::General, "%s: Cannot re-declare object with postfix [%s].",
|
|
getFileLine(ip), newName.c_str() );
|
|
ip = failJump;
|
|
STR.popFrame();
|
|
CSTK.popFrame();
|
|
break;
|
|
}
|
|
else
|
|
objectName = StringTable->insert( newName );
|
|
}
|
|
else
|
|
{
|
|
Con::errorf(ConsoleLogEntry::General, "%s: Cannot re-declare object [%s].",
|
|
getFileLine(ip), objectName);
|
|
ip = failJump;
|
|
STR.popFrame();
|
|
CSTK.popFrame();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
STR.popFrame();
|
|
CSTK.popFrame();
|
|
|
|
if(!currentNewObject)
|
|
{
|
|
// Well, looks like we have to create a new object.
|
|
ConsoleObject *object = ConsoleObject::create((const char*)callArgv[1]);
|
|
|
|
// Deal with failure!
|
|
if(!object)
|
|
{
|
|
Con::errorf(ConsoleLogEntry::General, "%s: Unable to instantiate non-conobject class %s.", getFileLine(ip), (const char*)callArgv[1]);
|
|
ip = failJump;
|
|
break;
|
|
}
|
|
|
|
// Do special datablock init if appropros
|
|
if(isDataBlock)
|
|
{
|
|
SimDataBlock *dataBlock = dynamic_cast<SimDataBlock *>(object);
|
|
if(dataBlock)
|
|
{
|
|
dataBlock->assignId();
|
|
}
|
|
else
|
|
{
|
|
// They tried to make a non-datablock with a datablock keyword!
|
|
Con::errorf(ConsoleLogEntry::General, "%s: Unable to instantiate non-datablock class %s.", getFileLine(ip), (const char*)callArgv[1]);
|
|
// Clean up...
|
|
delete object;
|
|
ip = failJump;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Finally, set currentNewObject to point to the new one.
|
|
currentNewObject = dynamic_cast<SimObject *>(object);
|
|
|
|
// Deal with the case of a non-SimObject.
|
|
if(!currentNewObject)
|
|
{
|
|
Con::errorf(ConsoleLogEntry::General, "%s: Unable to instantiate non-SimObject class %s.", getFileLine(ip), (const char*)callArgv[1]);
|
|
delete object;
|
|
ip = failJump;
|
|
break;
|
|
}
|
|
|
|
// Set the declaration line
|
|
currentNewObject->setDeclarationLine(lineNumber);
|
|
|
|
// Set the file that this object was created in
|
|
currentNewObject->setFilename(name);
|
|
|
|
// Does it have a parent object? (ie, the copy constructor : syntax, not inheriance)
|
|
if(*objParent)
|
|
{
|
|
// Find it!
|
|
SimObject *parent;
|
|
if(Sim::findObject(objParent, parent))
|
|
{
|
|
// Con::printf(" - Parent object found: %s", parent->getClassName());
|
|
|
|
currentNewObject->setCopySource( parent );
|
|
currentNewObject->assignFieldsFrom( parent );
|
|
}
|
|
else
|
|
{
|
|
if ( Con::gObjectCopyFailures == -1 )
|
|
Con::errorf(ConsoleLogEntry::General, "%s: Unable to find parent object %s for %s.", getFileLine(ip), objParent, (const char*)callArgv[1]);
|
|
else
|
|
++Con::gObjectCopyFailures;
|
|
|
|
// Fail to create the object.
|
|
delete object;
|
|
ip = failJump;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If a name was passed, assign it.
|
|
if( objectName[ 0 ] )
|
|
{
|
|
if( !isInternal )
|
|
currentNewObject->assignName( objectName );
|
|
else
|
|
currentNewObject->setInternalName( objectName );
|
|
|
|
// Set the original name
|
|
currentNewObject->setOriginalName( objectName );
|
|
}
|
|
|
|
// Prevent stack value corruption
|
|
CSTK.pushFrame();
|
|
STR.pushFrame();
|
|
// --
|
|
|
|
// Do the constructor parameters.
|
|
if(!currentNewObject->processArguments(callArgc-3, callArgv+3))
|
|
{
|
|
delete currentNewObject;
|
|
currentNewObject = NULL;
|
|
ip = failJump;
|
|
|
|
// Prevent stack value corruption
|
|
CSTK.popFrame();
|
|
STR.popFrame();
|
|
// --
|
|
break;
|
|
}
|
|
|
|
// Prevent stack value corruption
|
|
CSTK.popFrame();
|
|
STR.popFrame();
|
|
// --
|
|
|
|
// If it's not a datablock, allow people to modify bits of it.
|
|
if(!isDataBlock)
|
|
{
|
|
currentNewObject->setModStaticFields(true);
|
|
currentNewObject->setModDynamicFields(true);
|
|
}
|
|
}
|
|
|
|
// Advance the IP past the create info...
|
|
ip += 7;
|
|
break;
|
|
}
|
|
|
|
case OP_ADD_OBJECT:
|
|
{
|
|
// See OP_SETCURVAR for why we do this.
|
|
curFNDocBlock = NULL;
|
|
curNSDocBlock = NULL;
|
|
|
|
// Do we place this object at the root?
|
|
bool placeAtRoot = code[ip++];
|
|
|
|
// Con::printf("Adding object %s", currentNewObject->getName());
|
|
|
|
// Prevent stack value corruption
|
|
CSTK.pushFrame();
|
|
STR.pushFrame();
|
|
// --
|
|
|
|
// Make sure it wasn't already added, then add it.
|
|
if(currentNewObject->isProperlyAdded() == false)
|
|
{
|
|
bool ret = false;
|
|
|
|
Message *msg = dynamic_cast<Message *>(currentNewObject);
|
|
if(msg)
|
|
{
|
|
SimObjectId id = Message::getNextMessageID();
|
|
if(id != 0xffffffff)
|
|
ret = currentNewObject->registerObject(id);
|
|
else
|
|
Con::errorf("%s: No more object IDs available for messages", getFileLine(ip));
|
|
}
|
|
else
|
|
ret = currentNewObject->registerObject();
|
|
|
|
if(! ret)
|
|
{
|
|
// This error is usually caused by failing to call Parent::initPersistFields in the class' initPersistFields().
|
|
Con::warnf(ConsoleLogEntry::General, "%s: Register object failed for object %s of class %s.", getFileLine(ip), currentNewObject->getName(), currentNewObject->getClassName());
|
|
delete currentNewObject;
|
|
ip = failJump;
|
|
// Prevent stack value corruption
|
|
CSTK.popFrame();
|
|
STR.popFrame();
|
|
// --
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Are we dealing with a datablock?
|
|
SimDataBlock *dataBlock = dynamic_cast<SimDataBlock *>(currentNewObject);
|
|
static String errorStr;
|
|
|
|
|
|
|
|
// If so, preload it.
|
|
if(dataBlock && !dataBlock->preload(true, errorStr))
|
|
{
|
|
Con::errorf(ConsoleLogEntry::General, "%s: preload failed for %s: %s.", getFileLine(ip),
|
|
currentNewObject->getName(), errorStr.c_str());
|
|
dataBlock->deleteObject();
|
|
ip = failJump;
|
|
|
|
// Prevent stack value corruption
|
|
CSTK.popFrame();
|
|
STR.popFrame();
|
|
// --
|
|
break;
|
|
}
|
|
|
|
// What group will we be added to, if any?
|
|
U32 groupAddId = intStack[_UINT];
|
|
SimGroup *grp = NULL;
|
|
SimSet *set = NULL;
|
|
bool isMessage = dynamic_cast<Message *>(currentNewObject) != NULL;
|
|
|
|
if(!placeAtRoot || !currentNewObject->getGroup())
|
|
{
|
|
if(! isMessage)
|
|
{
|
|
if(! placeAtRoot)
|
|
{
|
|
// Otherwise just add to the requested group or set.
|
|
if(!Sim::findObject(groupAddId, grp))
|
|
Sim::findObject(groupAddId, set);
|
|
}
|
|
|
|
if(placeAtRoot)
|
|
{
|
|
// Deal with the instantGroup if we're being put at the root or we're adding to a component.
|
|
if( Con::gInstantGroup.isEmpty()
|
|
|| !Sim::findObject( Con::gInstantGroup, grp ) )
|
|
grp = Sim::getRootGroup();
|
|
}
|
|
}
|
|
|
|
// If we didn't get a group, then make sure we have a pointer to
|
|
// the rootgroup.
|
|
if(!grp)
|
|
grp = Sim::getRootGroup();
|
|
|
|
// add to the parent group
|
|
grp->addObject(currentNewObject);
|
|
|
|
// If for some reason the add failed, add the object to the
|
|
// root group so it won't leak.
|
|
if( !currentNewObject->getGroup() )
|
|
Sim::getRootGroup()->addObject( currentNewObject );
|
|
|
|
// add to any set we might be in
|
|
if(set)
|
|
set->addObject(currentNewObject);
|
|
}
|
|
|
|
// store the new object's ID on the stack (overwriting the group/set
|
|
// id, if one was given, otherwise getting pushed)
|
|
if(placeAtRoot)
|
|
intStack[_UINT] = currentNewObject->getId();
|
|
else
|
|
intStack[++_UINT] = currentNewObject->getId();
|
|
|
|
// Prevent stack value corruption
|
|
CSTK.popFrame();
|
|
STR.popFrame();
|
|
// --
|
|
break;
|
|
}
|
|
|
|
case OP_END_OBJECT:
|
|
{
|
|
// If we're not to be placed at the root, make sure we clean up
|
|
// our group reference.
|
|
bool placeAtRoot = code[ip++];
|
|
if(!placeAtRoot)
|
|
_UINT--;
|
|
break;
|
|
}
|
|
|
|
case OP_FINISH_OBJECT:
|
|
{
|
|
//Assert( objectCreationStackIndex >= 0 );
|
|
// Restore the object info from the stack [7/9/2007 Black]
|
|
currentNewObject = objectCreationStack[ --objectCreationStackIndex ].newObject;
|
|
failJump = objectCreationStack[ objectCreationStackIndex ].failJump;
|
|
break;
|
|
}
|
|
|
|
case OP_JMPIFFNOT:
|
|
if(floatStack[_FLT--])
|
|
{
|
|
ip++;
|
|
break;
|
|
}
|
|
ip = code[ip];
|
|
break;
|
|
case OP_JMPIFNOT:
|
|
if(intStack[_UINT--])
|
|
{
|
|
ip++;
|
|
break;
|
|
}
|
|
ip = code[ip];
|
|
break;
|
|
case OP_JMPIFF:
|
|
if(!floatStack[_FLT--])
|
|
{
|
|
ip++;
|
|
break;
|
|
}
|
|
ip = code[ip];
|
|
break;
|
|
case OP_JMPIF:
|
|
if(!intStack[_UINT--])
|
|
{
|
|
ip ++;
|
|
break;
|
|
}
|
|
ip = code[ip];
|
|
break;
|
|
case OP_JMPIFNOT_NP:
|
|
if(intStack[_UINT])
|
|
{
|
|
_UINT--;
|
|
ip++;
|
|
break;
|
|
}
|
|
ip = code[ip];
|
|
break;
|
|
case OP_JMPIF_NP:
|
|
if(!intStack[_UINT])
|
|
{
|
|
_UINT--;
|
|
ip++;
|
|
break;
|
|
}
|
|
ip = code[ip];
|
|
break;
|
|
case OP_JMP:
|
|
ip = code[ip];
|
|
break;
|
|
|
|
// This fixes a bug when not explicitly returning a value.
|
|
case OP_RETURN_VOID:
|
|
STR.setStringValue("");
|
|
// We're falling thru here on purpose.
|
|
|
|
case OP_RETURN:
|
|
retValue = STR.getStringValue();
|
|
|
|
if( iterDepth > 0 )
|
|
{
|
|
// Clear iterator state.
|
|
while( iterDepth > 0 )
|
|
{
|
|
iterStack[ -- _ITER ].mIsStringIter = false;
|
|
-- iterDepth;
|
|
}
|
|
|
|
STR.rewind();
|
|
STR.setStringValue( retValue ); // Not nice but works.
|
|
retValue = STR.getStringValue();
|
|
}
|
|
|
|
// Previously the return value was on the stack and would be returned using STR.getStringValue().
|
|
// Now though we need to wrap it in a ConsoleValueRef
|
|
returnValue.value = CSTK.pushStackString(retValue);
|
|
|
|
goto execFinished;
|
|
|
|
case OP_RETURN_FLT:
|
|
|
|
if( iterDepth > 0 )
|
|
{
|
|
// Clear iterator state.
|
|
while( iterDepth > 0 )
|
|
{
|
|
iterStack[ -- _ITER ].mIsStringIter = false;
|
|
-- iterDepth;
|
|
}
|
|
|
|
}
|
|
|
|
returnValue.value = CSTK.pushFLT(floatStack[_FLT]);
|
|
_FLT--;
|
|
|
|
goto execFinished;
|
|
|
|
case OP_RETURN_UINT:
|
|
|
|
if( iterDepth > 0 )
|
|
{
|
|
// Clear iterator state.
|
|
while( iterDepth > 0 )
|
|
{
|
|
iterStack[ -- _ITER ].mIsStringIter = false;
|
|
-- iterDepth;
|
|
}
|
|
}
|
|
|
|
returnValue.value = CSTK.pushUINT(intStack[_UINT]);
|
|
_UINT--;
|
|
|
|
goto execFinished;
|
|
|
|
case OP_CMPEQ:
|
|
intStack[_UINT+1] = bool(floatStack[_FLT] == floatStack[_FLT-1]);
|
|
_UINT++;
|
|
_FLT -= 2;
|
|
break;
|
|
|
|
case OP_CMPGR:
|
|
intStack[_UINT+1] = bool(floatStack[_FLT] > floatStack[_FLT-1]);
|
|
_UINT++;
|
|
_FLT -= 2;
|
|
break;
|
|
|
|
case OP_CMPGE:
|
|
intStack[_UINT+1] = bool(floatStack[_FLT] >= floatStack[_FLT-1]);
|
|
_UINT++;
|
|
_FLT -= 2;
|
|
break;
|
|
|
|
case OP_CMPLT:
|
|
intStack[_UINT+1] = bool(floatStack[_FLT] < floatStack[_FLT-1]);
|
|
_UINT++;
|
|
_FLT -= 2;
|
|
break;
|
|
|
|
case OP_CMPLE:
|
|
intStack[_UINT+1] = bool(floatStack[_FLT] <= floatStack[_FLT-1]);
|
|
_UINT++;
|
|
_FLT -= 2;
|
|
break;
|
|
|
|
case OP_CMPNE:
|
|
intStack[_UINT+1] = bool(floatStack[_FLT] != floatStack[_FLT-1]);
|
|
_UINT++;
|
|
_FLT -= 2;
|
|
break;
|
|
|
|
case OP_XOR:
|
|
intStack[_UINT-1] = intStack[_UINT] ^ intStack[_UINT-1];
|
|
_UINT--;
|
|
break;
|
|
|
|
case OP_MOD:
|
|
if( intStack[_UINT-1] != 0 )
|
|
intStack[_UINT-1] = intStack[_UINT] % intStack[_UINT-1];
|
|
else
|
|
intStack[_UINT-1] = 0;
|
|
_UINT--;
|
|
break;
|
|
|
|
case OP_BITAND:
|
|
intStack[_UINT-1] = intStack[_UINT] & intStack[_UINT-1];
|
|
_UINT--;
|
|
break;
|
|
|
|
case OP_BITOR:
|
|
intStack[_UINT-1] = intStack[_UINT] | intStack[_UINT-1];
|
|
_UINT--;
|
|
break;
|
|
|
|
case OP_NOT:
|
|
intStack[_UINT] = !intStack[_UINT];
|
|
break;
|
|
|
|
case OP_NOTF:
|
|
intStack[_UINT+1] = !floatStack[_FLT];
|
|
_FLT--;
|
|
_UINT++;
|
|
break;
|
|
|
|
case OP_ONESCOMPLEMENT:
|
|
intStack[_UINT] = ~intStack[_UINT];
|
|
break;
|
|
|
|
case OP_SHR:
|
|
intStack[_UINT-1] = intStack[_UINT] >> intStack[_UINT-1];
|
|
_UINT--;
|
|
break;
|
|
|
|
case OP_SHL:
|
|
intStack[_UINT-1] = intStack[_UINT] << intStack[_UINT-1];
|
|
_UINT--;
|
|
break;
|
|
|
|
case OP_AND:
|
|
intStack[_UINT-1] = intStack[_UINT] && intStack[_UINT-1];
|
|
_UINT--;
|
|
break;
|
|
|
|
case OP_OR:
|
|
intStack[_UINT-1] = intStack[_UINT] || intStack[_UINT-1];
|
|
_UINT--;
|
|
break;
|
|
|
|
case OP_ADD:
|
|
floatStack[_FLT-1] = floatStack[_FLT] + floatStack[_FLT-1];
|
|
_FLT--;
|
|
break;
|
|
|
|
case OP_SUB:
|
|
floatStack[_FLT-1] = floatStack[_FLT] - floatStack[_FLT-1];
|
|
_FLT--;
|
|
break;
|
|
|
|
case OP_MUL:
|
|
floatStack[_FLT-1] = floatStack[_FLT] * floatStack[_FLT-1];
|
|
_FLT--;
|
|
break;
|
|
case OP_DIV:
|
|
floatStack[_FLT-1] = floatStack[_FLT] / floatStack[_FLT-1];
|
|
_FLT--;
|
|
break;
|
|
case OP_NEG:
|
|
floatStack[_FLT] = -floatStack[_FLT];
|
|
break;
|
|
|
|
case OP_SETCURVAR:
|
|
var = CodeToSTE(code, ip);
|
|
ip += 2;
|
|
|
|
// If a variable is set, then these must be NULL. It is necessary
|
|
// to set this here so that the vector parser can appropriately
|
|
// identify whether it's dealing with a vector.
|
|
prevField = NULL;
|
|
prevObject = NULL;
|
|
curObject = NULL;
|
|
|
|
gEvalState.setCurVarName(var);
|
|
|
|
// In order to let docblocks work properly with variables, we have
|
|
// clear the current docblock when we do an assign. This way it
|
|
// won't inappropriately carry forward to following function decls.
|
|
curFNDocBlock = NULL;
|
|
curNSDocBlock = NULL;
|
|
break;
|
|
|
|
case OP_SETCURVAR_CREATE:
|
|
var = CodeToSTE(code, ip);
|
|
ip += 2;
|
|
|
|
// See OP_SETCURVAR
|
|
prevField = NULL;
|
|
prevObject = NULL;
|
|
curObject = NULL;
|
|
|
|
gEvalState.setCurVarNameCreate(var);
|
|
|
|
// See OP_SETCURVAR for why we do this.
|
|
curFNDocBlock = NULL;
|
|
curNSDocBlock = NULL;
|
|
break;
|
|
|
|
case OP_SETCURVAR_ARRAY:
|
|
var = STR.getSTValue();
|
|
|
|
// See OP_SETCURVAR
|
|
prevField = NULL;
|
|
prevObject = NULL;
|
|
curObject = NULL;
|
|
|
|
gEvalState.setCurVarName(var);
|
|
|
|
// See OP_SETCURVAR for why we do this.
|
|
curFNDocBlock = NULL;
|
|
curNSDocBlock = NULL;
|
|
break;
|
|
|
|
case OP_SETCURVAR_ARRAY_CREATE:
|
|
var = STR.getSTValue();
|
|
|
|
// See OP_SETCURVAR
|
|
prevField = NULL;
|
|
prevObject = NULL;
|
|
curObject = NULL;
|
|
|
|
gEvalState.setCurVarNameCreate(var);
|
|
|
|
// See OP_SETCURVAR for why we do this.
|
|
curFNDocBlock = NULL;
|
|
curNSDocBlock = NULL;
|
|
break;
|
|
|
|
case OP_LOADVAR_UINT:
|
|
intStack[_UINT+1] = gEvalState.getIntVariable();
|
|
_UINT++;
|
|
break;
|
|
|
|
case OP_LOADVAR_FLT:
|
|
floatStack[_FLT+1] = gEvalState.getFloatVariable();
|
|
_FLT++;
|
|
break;
|
|
|
|
case OP_LOADVAR_STR:
|
|
val = gEvalState.getStringVariable();
|
|
STR.setStringValue(val);
|
|
break;
|
|
|
|
case OP_LOADVAR_VAR:
|
|
// Sets current source of OP_SAVEVAR_VAR
|
|
gEvalState.copyVariable = gEvalState.currentVariable;
|
|
break;
|
|
|
|
case OP_SAVEVAR_UINT:
|
|
gEvalState.setIntVariable(intStack[_UINT]);
|
|
break;
|
|
|
|
case OP_SAVEVAR_FLT:
|
|
gEvalState.setFloatVariable(floatStack[_FLT]);
|
|
break;
|
|
|
|
case OP_SAVEVAR_STR:
|
|
gEvalState.setStringVariable(STR.getStringValue());
|
|
break;
|
|
|
|
case OP_SAVEVAR_VAR:
|
|
// this basically handles %var1 = %var2
|
|
gEvalState.setCopyVariable();
|
|
break;
|
|
|
|
case OP_SETCUROBJECT:
|
|
// Save the previous object for parsing vector fields.
|
|
prevObject = curObject;
|
|
val = STR.getStringValue();
|
|
|
|
// Sim::findObject will sometimes find valid objects from
|
|
// multi-component strings. This makes sure that doesn't
|
|
// happen.
|
|
for( const char* check = val; *check; check++ )
|
|
{
|
|
if( *check == ' ' )
|
|
{
|
|
val = "";
|
|
break;
|
|
}
|
|
}
|
|
curObject = Sim::findObject(val);
|
|
break;
|
|
|
|
case OP_SETCUROBJECT_INTERNAL:
|
|
++ip; // To skip the recurse flag if the object wasn't found
|
|
if(curObject)
|
|
{
|
|
SimSet *set = dynamic_cast<SimSet *>(curObject);
|
|
if(set)
|
|
{
|
|
StringTableEntry intName = StringTable->insert(STR.getStringValue());
|
|
bool recurse = code[ip-1];
|
|
SimObject *obj = set->findObjectByInternalName(intName, recurse);
|
|
intStack[_UINT+1] = obj ? obj->getId() : 0;
|
|
_UINT++;
|
|
}
|
|
else
|
|
{
|
|
Con::errorf(ConsoleLogEntry::Script, "%s: Attempt to use -> on non-set %s of class %s.", getFileLine(ip-2), curObject->getName(), curObject->getClassName());
|
|
intStack[_UINT] = 0;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case OP_SETCUROBJECT_NEW:
|
|
curObject = currentNewObject;
|
|
break;
|
|
|
|
case OP_SETCURFIELD:
|
|
// Save the previous field for parsing vector fields.
|
|
prevField = curField;
|
|
dStrcpy( prevFieldArray, curFieldArray );
|
|
curField = CodeToSTE(code, ip);
|
|
curFieldArray[0] = 0;
|
|
ip += 2;
|
|
break;
|
|
|
|
case OP_SETCURFIELD_ARRAY:
|
|
dStrcpy(curFieldArray, STR.getStringValue());
|
|
break;
|
|
|
|
case OP_SETCURFIELD_TYPE:
|
|
if(curObject)
|
|
curObject->setDataFieldType(code[ip], curField, curFieldArray);
|
|
ip++;
|
|
break;
|
|
|
|
case OP_LOADFIELD_UINT:
|
|
if(curObject)
|
|
intStack[_UINT+1] = U32(dAtoi(curObject->getDataField(curField, curFieldArray)));
|
|
else
|
|
{
|
|
// The field is not being retrieved from an object. Maybe it's
|
|
// a special accessor?
|
|
getFieldComponent( prevObject, prevField, prevFieldArray, curField, valBuffer );
|
|
intStack[_UINT+1] = dAtoi( valBuffer );
|
|
}
|
|
_UINT++;
|
|
break;
|
|
|
|
case OP_LOADFIELD_FLT:
|
|
if(curObject)
|
|
floatStack[_FLT+1] = dAtof(curObject->getDataField(curField, curFieldArray));
|
|
else
|
|
{
|
|
// The field is not being retrieved from an object. Maybe it's
|
|
// a special accessor?
|
|
getFieldComponent( prevObject, prevField, prevFieldArray, curField, valBuffer );
|
|
floatStack[_FLT+1] = dAtof( valBuffer );
|
|
}
|
|
_FLT++;
|
|
break;
|
|
|
|
case OP_LOADFIELD_STR:
|
|
if(curObject)
|
|
{
|
|
val = curObject->getDataField(curField, curFieldArray);
|
|
STR.setStringValue( val );
|
|
}
|
|
else
|
|
{
|
|
// The field is not being retrieved from an object. Maybe it's
|
|
// a special accessor?
|
|
getFieldComponent( prevObject, prevField, prevFieldArray, curField, valBuffer );
|
|
STR.setStringValue( valBuffer );
|
|
}
|
|
break;
|
|
|
|
case OP_SAVEFIELD_UINT:
|
|
STR.setIntValue(intStack[_UINT]);
|
|
if(curObject)
|
|
curObject->setDataField(curField, curFieldArray, STR.getStringValue());
|
|
else
|
|
{
|
|
// The field is not being set on an object. Maybe it's
|
|
// a special accessor?
|
|
setFieldComponent( prevObject, prevField, prevFieldArray, curField );
|
|
prevObject = NULL;
|
|
}
|
|
break;
|
|
|
|
case OP_SAVEFIELD_FLT:
|
|
STR.setFloatValue(floatStack[_FLT]);
|
|
if(curObject)
|
|
curObject->setDataField(curField, curFieldArray, STR.getStringValue());
|
|
else
|
|
{
|
|
// The field is not being set on an object. Maybe it's
|
|
// a special accessor?
|
|
setFieldComponent( prevObject, prevField, prevFieldArray, curField );
|
|
prevObject = NULL;
|
|
}
|
|
break;
|
|
|
|
case OP_SAVEFIELD_STR:
|
|
if(curObject)
|
|
curObject->setDataField(curField, curFieldArray, STR.getStringValue());
|
|
else
|
|
{
|
|
// The field is not being set on an object. Maybe it's
|
|
// a special accessor?
|
|
setFieldComponent( prevObject, prevField, prevFieldArray, curField );
|
|
prevObject = NULL;
|
|
}
|
|
break;
|
|
|
|
case OP_STR_TO_UINT:
|
|
intStack[_UINT+1] = STR.getIntValue();
|
|
_UINT++;
|
|
break;
|
|
|
|
case OP_STR_TO_FLT:
|
|
floatStack[_FLT+1] = STR.getFloatValue();
|
|
_FLT++;
|
|
break;
|
|
|
|
case OP_STR_TO_NONE:
|
|
// This exists simply to deal with certain typecast situations.
|
|
break;
|
|
|
|
case OP_FLT_TO_UINT:
|
|
intStack[_UINT+1] = (S64)floatStack[_FLT];
|
|
_FLT--;
|
|
_UINT++;
|
|
break;
|
|
|
|
case OP_FLT_TO_STR:
|
|
STR.setFloatValue(floatStack[_FLT]);
|
|
_FLT--;
|
|
break;
|
|
|
|
case OP_FLT_TO_NONE:
|
|
_FLT--;
|
|
break;
|
|
|
|
case OP_UINT_TO_FLT:
|
|
floatStack[_FLT+1] = (F32)intStack[_UINT];
|
|
_UINT--;
|
|
_FLT++;
|
|
break;
|
|
|
|
case OP_UINT_TO_STR:
|
|
STR.setIntValue(intStack[_UINT]);
|
|
_UINT--;
|
|
break;
|
|
|
|
case OP_UINT_TO_NONE:
|
|
_UINT--;
|
|
break;
|
|
|
|
case OP_COPYVAR_TO_NONE:
|
|
gEvalState.copyVariable = NULL;
|
|
break;
|
|
|
|
case OP_LOADIMMED_UINT:
|
|
intStack[_UINT+1] = code[ip++];
|
|
_UINT++;
|
|
break;
|
|
|
|
case OP_LOADIMMED_FLT:
|
|
floatStack[_FLT+1] = curFloatTable[code[ip]];
|
|
ip++;
|
|
_FLT++;
|
|
break;
|
|
|
|
case OP_TAG_TO_STR:
|
|
code[ip-1] = OP_LOADIMMED_STR;
|
|
// it's possible the string has already been converted
|
|
if(U8(curStringTable[code[ip]]) != StringTagPrefixByte)
|
|
{
|
|
U32 id = GameAddTaggedString(curStringTable + code[ip]);
|
|
dSprintf(curStringTable + code[ip] + 1, 7, "%d", id);
|
|
*(curStringTable + code[ip]) = StringTagPrefixByte;
|
|
}
|
|
case OP_LOADIMMED_STR:
|
|
STR.setStringValue(curStringTable + code[ip++]);
|
|
break;
|
|
|
|
case OP_DOCBLOCK_STR:
|
|
{
|
|
// If the first word of the doc is '\class' or '@class', then this
|
|
// is a namespace doc block, otherwise it is a function doc block.
|
|
const char* docblock = curStringTable + code[ip++];
|
|
|
|
const char* sansClass = dStrstr( docblock, "@class" );
|
|
if( !sansClass )
|
|
sansClass = dStrstr( docblock, "\\class" );
|
|
|
|
if( sansClass )
|
|
{
|
|
// Don't save the class declaration. Scan past the 'class'
|
|
// keyword and up to the first whitespace.
|
|
sansClass += 7;
|
|
S32 index = 0;
|
|
while( ( *sansClass != ' ' ) && ( *sansClass != '\n' ) && *sansClass && ( index < ( nsDocLength - 1 ) ) )
|
|
{
|
|
nsDocBlockClass[index++] = *sansClass;
|
|
sansClass++;
|
|
}
|
|
nsDocBlockClass[index] = '\0';
|
|
|
|
curNSDocBlock = sansClass + 1;
|
|
}
|
|
else
|
|
curFNDocBlock = docblock;
|
|
}
|
|
|
|
break;
|
|
|
|
case OP_LOADIMMED_IDENT:
|
|
STR.setStringValue(CodeToSTE(code, ip));
|
|
ip += 2;
|
|
break;
|
|
|
|
case OP_CALLFUNC_RESOLVE:
|
|
// This deals with a function that is potentially living in a namespace.
|
|
fnNamespace = CodeToSTE(code, ip+2);
|
|
fnName = CodeToSTE(code, ip);
|
|
|
|
// Try to look it up.
|
|
ns = Namespace::find(fnNamespace);
|
|
nsEntry = ns->lookup(fnName);
|
|
if(!nsEntry)
|
|
{
|
|
ip+= 5;
|
|
Con::warnf(ConsoleLogEntry::General,
|
|
"%s: Unable to find function %s%s%s",
|
|
getFileLine(ip-7), fnNamespace ? fnNamespace : "",
|
|
fnNamespace ? "::" : "", fnName);
|
|
STR.popFrame();
|
|
CSTK.popFrame();
|
|
break;
|
|
}
|
|
|
|
#ifdef COMPILER_OPTIMIZE_FUNCTION_CALLS
|
|
// Now fall through to OP_CALLFUNC...
|
|
// Now, rewrite our code a bit (ie, avoid future lookups) and fall
|
|
// through to OP_CALLFUNC
|
|
#ifdef TORQUE_CPU_X64
|
|
*((U64*)(code+ip+2)) = ((U64)nsEntry);
|
|
#else
|
|
code[ip+2] = ((U32)nsEntry);
|
|
#endif
|
|
code[ip-1] = OP_CALLFUNC;
|
|
#endif
|
|
|
|
case OP_CALLFUNC:
|
|
{
|
|
// This routingId is set when we query the object as to whether
|
|
// it handles this method. It is set to an enum from the table
|
|
// above indicating whether it handles it on a component it owns
|
|
// or just on the object.
|
|
S32 routingId = 0;
|
|
|
|
fnName = CodeToSTE(code, ip);
|
|
|
|
//if this is called from inside a function, append the ip and codeptr
|
|
if( gEvalState.getStackDepth() > 0 )
|
|
{
|
|
gEvalState.getCurrentFrame().code = this;
|
|
gEvalState.getCurrentFrame().ip = ip - 1;
|
|
}
|
|
|
|
U32 callType = code[ip+4];
|
|
|
|
ip += 5;
|
|
CSTK.getArgcArgv(fnName, &callArgc, &callArgv);
|
|
|
|
const char *componentReturnValue = "";
|
|
|
|
if(callType == FuncCallExprNode::FunctionCall)
|
|
{
|
|
if( !nsEntry )
|
|
{
|
|
#ifdef COMPILER_OPTIMIZE_FUNCTION_CALLS
|
|
#ifdef TORQUE_CPU_X64
|
|
nsEntry = ((Namespace::Entry *) *((U64*)(code+ip-3)));
|
|
#else
|
|
nsEntry = ((Namespace::Entry *) *(code+ip-3));
|
|
#endif
|
|
#else
|
|
nsEntry = Namespace::global()->lookup( fnName );
|
|
#endif
|
|
ns = NULL;
|
|
}
|
|
ns = NULL;
|
|
}
|
|
else if(callType == FuncCallExprNode::MethodCall)
|
|
{
|
|
saveObject = gEvalState.thisObject;
|
|
gEvalState.thisObject = Sim::findObject((const char*)callArgv[1]);
|
|
if(!gEvalState.thisObject)
|
|
{
|
|
// Go back to the previous saved object.
|
|
gEvalState.thisObject = saveObject;
|
|
|
|
Con::warnf(ConsoleLogEntry::General,"%s: Unable to find object: '%s' attempting to call function '%s'", getFileLine(ip-4), (const char*)callArgv[1], fnName);
|
|
STR.popFrame();
|
|
CSTK.popFrame();
|
|
break;
|
|
}
|
|
|
|
bool handlesMethod = gEvalState.thisObject->handlesConsoleMethod(fnName,&routingId);
|
|
if( handlesMethod && routingId == MethodOnComponent )
|
|
{
|
|
ICallMethod *pComponent = dynamic_cast<ICallMethod *>( gEvalState.thisObject );
|
|
if( pComponent )
|
|
componentReturnValue = pComponent->callMethodArgList( callArgc, callArgv, false );
|
|
}
|
|
|
|
ns = gEvalState.thisObject->getNamespace();
|
|
if(ns)
|
|
nsEntry = ns->lookup(fnName);
|
|
else
|
|
nsEntry = NULL;
|
|
}
|
|
else // it's a ParentCall
|
|
{
|
|
if(thisNamespace)
|
|
{
|
|
ns = thisNamespace->mParent;
|
|
if(ns)
|
|
nsEntry = ns->lookup(fnName);
|
|
else
|
|
nsEntry = NULL;
|
|
}
|
|
else
|
|
{
|
|
ns = NULL;
|
|
nsEntry = NULL;
|
|
}
|
|
}
|
|
|
|
Namespace::Entry::CallbackUnion * nsCb = NULL;
|
|
const char * nsUsage = NULL;
|
|
if (nsEntry)
|
|
{
|
|
nsCb = &nsEntry->cb;
|
|
nsUsage = nsEntry->mUsage;
|
|
routingId = 0;
|
|
}
|
|
if(!nsEntry || noCalls)
|
|
{
|
|
if(!noCalls && !( routingId == MethodOnComponent ) )
|
|
{
|
|
Con::warnf(ConsoleLogEntry::General,"%s: Unknown command %s.", getFileLine(ip-6), fnName);
|
|
if(callType == FuncCallExprNode::MethodCall)
|
|
{
|
|
Con::warnf(ConsoleLogEntry::General, " Object %s(%d) %s",
|
|
gEvalState.thisObject->getName() ? gEvalState.thisObject->getName() : "",
|
|
gEvalState.thisObject->getId(), Con::getNamespaceList(ns) );
|
|
}
|
|
}
|
|
STR.popFrame();
|
|
CSTK.popFrame();
|
|
|
|
if( routingId == MethodOnComponent )
|
|
STR.setStringValue( componentReturnValue );
|
|
else
|
|
STR.setStringValue( "" );
|
|
|
|
break;
|
|
}
|
|
if(nsEntry->mType == Namespace::Entry::ConsoleFunctionType)
|
|
{
|
|
ConsoleValueRef ret;
|
|
if(nsEntry->mFunctionOffset)
|
|
ret = nsEntry->mCode->exec(nsEntry->mFunctionOffset, fnName, nsEntry->mNamespace, callArgc, callArgv, false, nsEntry->mPackage);
|
|
|
|
STR.popFrame();
|
|
// Functions are assumed to return strings, so look ahead to see if we can skip the conversion
|
|
if(code[ip] == OP_STR_TO_UINT)
|
|
{
|
|
ip++;
|
|
intStack[++_UINT] = (U32)((S32)ret);
|
|
}
|
|
else if(code[ip] == OP_STR_TO_FLT)
|
|
{
|
|
ip++;
|
|
floatStack[++_FLT] = (F32)ret;
|
|
}
|
|
else if(code[ip] == OP_STR_TO_NONE)
|
|
ip++;
|
|
else
|
|
STR.setStringValue((const char*)ret);
|
|
|
|
// This will clear everything including returnValue
|
|
CSTK.popFrame();
|
|
STR.clearFunctionOffset();
|
|
}
|
|
else
|
|
{
|
|
const char* nsName = ns? ns->mName: "";
|
|
#ifndef TORQUE_DEBUG
|
|
// [tom, 12/13/2006] This stops tools functions from working in the console,
|
|
// which is useful behavior when debugging so I'm ifdefing this out for debug builds.
|
|
if(nsEntry->mToolOnly && ! Con::isCurrentScriptToolScript())
|
|
{
|
|
Con::errorf(ConsoleLogEntry::Script, "%s: %s::%s - attempting to call tools only function from outside of tools.", getFileLine(ip-6), nsName, fnName);
|
|
}
|
|
else
|
|
#endif
|
|
if((nsEntry->mMinArgs && S32(callArgc) < nsEntry->mMinArgs) || (nsEntry->mMaxArgs && S32(callArgc) > nsEntry->mMaxArgs))
|
|
{
|
|
Con::warnf(ConsoleLogEntry::Script, "%s: %s::%s - wrong number of arguments (got %i, expected min %i and max %i).",
|
|
getFileLine(ip-6), nsName, fnName,
|
|
callArgc, nsEntry->mMinArgs, nsEntry->mMaxArgs);
|
|
Con::warnf(ConsoleLogEntry::Script, "%s: usage: %s", getFileLine(ip-6), nsEntry->mUsage);
|
|
STR.popFrame();
|
|
CSTK.popFrame();
|
|
}
|
|
else
|
|
{
|
|
switch(nsEntry->mType)
|
|
{
|
|
case Namespace::Entry::StringCallbackType:
|
|
{
|
|
const char *ret = nsEntry->cb.mStringCallbackFunc(gEvalState.thisObject, callArgc, callArgv);
|
|
STR.popFrame();
|
|
CSTK.popFrame();
|
|
if(ret != STR.getStringValue())
|
|
STR.setStringValue(ret);
|
|
//else
|
|
// STR.setLen(dStrlen(ret));
|
|
break;
|
|
}
|
|
case Namespace::Entry::IntCallbackType:
|
|
{
|
|
S32 result = nsEntry->cb.mIntCallbackFunc(gEvalState.thisObject, callArgc, callArgv);
|
|
STR.popFrame();
|
|
CSTK.popFrame();
|
|
if(code[ip] == OP_STR_TO_UINT)
|
|
{
|
|
ip++;
|
|
intStack[++_UINT] = result;
|
|
break;
|
|
}
|
|
else if(code[ip] == OP_STR_TO_FLT)
|
|
{
|
|
ip++;
|
|
floatStack[++_FLT] = result;
|
|
break;
|
|
}
|
|
else if(code[ip] == OP_STR_TO_NONE)
|
|
ip++;
|
|
else
|
|
STR.setIntValue(result);
|
|
break;
|
|
}
|
|
case Namespace::Entry::FloatCallbackType:
|
|
{
|
|
F64 result = nsEntry->cb.mFloatCallbackFunc(gEvalState.thisObject, callArgc, callArgv);
|
|
STR.popFrame();
|
|
CSTK.popFrame();
|
|
if(code[ip] == OP_STR_TO_UINT)
|
|
{
|
|
ip++;
|
|
intStack[++_UINT] = (S64)result;
|
|
break;
|
|
}
|
|
else if(code[ip] == OP_STR_TO_FLT)
|
|
{
|
|
ip++;
|
|
floatStack[++_FLT] = result;
|
|
break;
|
|
}
|
|
else if(code[ip] == OP_STR_TO_NONE)
|
|
ip++;
|
|
else
|
|
STR.setFloatValue(result);
|
|
break;
|
|
}
|
|
case Namespace::Entry::VoidCallbackType:
|
|
nsEntry->cb.mVoidCallbackFunc(gEvalState.thisObject, callArgc, callArgv);
|
|
if( code[ ip ] != OP_STR_TO_NONE && Con::getBoolVariable( "$Con::warnVoidAssignment", true ) )
|
|
Con::warnf(ConsoleLogEntry::General, "%s: Call to %s in %s uses result of void function call.", getFileLine(ip-6), fnName, functionName);
|
|
|
|
STR.popFrame();
|
|
CSTK.popFrame();
|
|
STR.setStringValue("");
|
|
break;
|
|
case Namespace::Entry::BoolCallbackType:
|
|
{
|
|
bool result = nsEntry->cb.mBoolCallbackFunc(gEvalState.thisObject, callArgc, callArgv);
|
|
STR.popFrame();
|
|
CSTK.popFrame();
|
|
if(code[ip] == OP_STR_TO_UINT)
|
|
{
|
|
ip++;
|
|
intStack[++_UINT] = result;
|
|
break;
|
|
}
|
|
else if(code[ip] == OP_STR_TO_FLT)
|
|
{
|
|
ip++;
|
|
floatStack[++_FLT] = result;
|
|
break;
|
|
}
|
|
else if(code[ip] == OP_STR_TO_NONE)
|
|
ip++;
|
|
else
|
|
STR.setIntValue(result);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if(callType == FuncCallExprNode::MethodCall)
|
|
gEvalState.thisObject = saveObject;
|
|
break;
|
|
}
|
|
case OP_ADVANCE_STR:
|
|
STR.advance();
|
|
break;
|
|
case OP_ADVANCE_STR_APPENDCHAR:
|
|
STR.advanceChar(code[ip++]);
|
|
break;
|
|
|
|
case OP_ADVANCE_STR_COMMA:
|
|
STR.advanceChar('_');
|
|
break;
|
|
|
|
case OP_ADVANCE_STR_NUL:
|
|
STR.advanceChar(0);
|
|
break;
|
|
|
|
case OP_REWIND_STR:
|
|
STR.rewind();
|
|
break;
|
|
|
|
case OP_TERMINATE_REWIND_STR:
|
|
STR.rewindTerminate();
|
|
break;
|
|
|
|
case OP_COMPARE_STR:
|
|
intStack[++_UINT] = STR.compare();
|
|
break;
|
|
case OP_PUSH:
|
|
STR.push();
|
|
CSTK.pushString(STR.getPreviousStringValue());
|
|
break;
|
|
case OP_PUSH_UINT:
|
|
CSTK.pushUINT(intStack[_UINT]);
|
|
_UINT--;
|
|
break;
|
|
case OP_PUSH_FLT:
|
|
CSTK.pushFLT(floatStack[_FLT]);
|
|
_FLT--;
|
|
break;
|
|
case OP_PUSH_VAR:
|
|
if (gEvalState.currentVariable)
|
|
CSTK.pushValue(gEvalState.currentVariable->value);
|
|
else
|
|
CSTK.pushString("");
|
|
break;
|
|
|
|
case OP_PUSH_FRAME:
|
|
STR.pushFrame();
|
|
CSTK.pushFrame();
|
|
break;
|
|
|
|
case OP_ASSERT:
|
|
{
|
|
if( !intStack[_UINT--] )
|
|
{
|
|
const char *message = curStringTable + code[ip];
|
|
|
|
U32 breakLine, inst;
|
|
findBreakLine( ip - 1, breakLine, inst );
|
|
|
|
if ( PlatformAssert::processAssert( PlatformAssert::Fatal,
|
|
name ? name : "eval",
|
|
breakLine,
|
|
message ) )
|
|
{
|
|
if ( TelDebugger && TelDebugger->isConnected() && breakLine > 0 )
|
|
{
|
|
TelDebugger->breakProcess();
|
|
}
|
|
else
|
|
Platform::debugBreak();
|
|
}
|
|
}
|
|
|
|
ip++;
|
|
break;
|
|
}
|
|
|
|
case OP_BREAK:
|
|
{
|
|
//append the ip and codeptr before managing the breakpoint!
|
|
AssertFatal( gEvalState.getStackDepth() > 0, "Empty eval stack on break!");
|
|
gEvalState.getCurrentFrame().code = this;
|
|
gEvalState.getCurrentFrame().ip = ip - 1;
|
|
|
|
U32 breakLine;
|
|
findBreakLine(ip-1, breakLine, instruction);
|
|
if(!breakLine)
|
|
goto breakContinue;
|
|
TelDebugger->executionStopped(this, breakLine);
|
|
goto breakContinue;
|
|
}
|
|
|
|
case OP_ITER_BEGIN_STR:
|
|
{
|
|
iterStack[ _ITER ].mIsStringIter = true;
|
|
/* fallthrough */
|
|
}
|
|
|
|
case OP_ITER_BEGIN:
|
|
{
|
|
StringTableEntry varName = CodeToSTE(code, ip);
|
|
U32 failIp = code[ ip + 2 ];
|
|
|
|
IterStackRecord& iter = iterStack[ _ITER ];
|
|
|
|
iter.mVariable = gEvalState.getCurrentFrame().add( varName );
|
|
|
|
if( iter.mIsStringIter )
|
|
{
|
|
iter.mData.mStr.mString = STR.getStringValue();
|
|
iter.mData.mStr.mIndex = 0;
|
|
}
|
|
else
|
|
{
|
|
// Look up the object.
|
|
|
|
SimSet* set;
|
|
if( !Sim::findObject( STR.getStringValue(), set ) )
|
|
{
|
|
Con::errorf( ConsoleLogEntry::General, "No SimSet object '%s'", STR.getStringValue() );
|
|
Con::errorf( ConsoleLogEntry::General, "Did you mean to use 'foreach$' instead of 'foreach'?" );
|
|
ip = failIp;
|
|
continue;
|
|
}
|
|
|
|
// Set up.
|
|
|
|
iter.mData.mObj.mSet = set;
|
|
iter.mData.mObj.mIndex = 0;
|
|
}
|
|
|
|
_ITER ++;
|
|
iterDepth ++;
|
|
|
|
STR.push();
|
|
|
|
ip += 3;
|
|
break;
|
|
}
|
|
|
|
case OP_ITER:
|
|
{
|
|
U32 breakIp = code[ ip ];
|
|
IterStackRecord& iter = iterStack[ _ITER - 1 ];
|
|
|
|
if( iter.mIsStringIter )
|
|
{
|
|
const char* str = iter.mData.mStr.mString;
|
|
|
|
U32 startIndex = iter.mData.mStr.mIndex;
|
|
U32 endIndex = startIndex;
|
|
|
|
// Break if at end.
|
|
|
|
if( !str[ startIndex ] )
|
|
{
|
|
ip = breakIp;
|
|
continue;
|
|
}
|
|
|
|
// Find right end of current component.
|
|
|
|
if( !dIsspace( str[ endIndex ] ) )
|
|
do ++ endIndex;
|
|
while( str[ endIndex ] && !dIsspace( str[ endIndex ] ) );
|
|
|
|
// Extract component.
|
|
|
|
if( endIndex != startIndex )
|
|
{
|
|
char savedChar = str[ endIndex ];
|
|
const_cast< char* >( str )[ endIndex ] = '\0'; // We are on the string stack so this is okay.
|
|
iter.mVariable->setStringValue( &str[ startIndex ] );
|
|
const_cast< char* >( str )[ endIndex ] = savedChar;
|
|
}
|
|
else
|
|
iter.mVariable->setStringValue( "" );
|
|
|
|
// Skip separator.
|
|
if( str[ endIndex ] != '\0' )
|
|
++ endIndex;
|
|
|
|
iter.mData.mStr.mIndex = endIndex;
|
|
}
|
|
else
|
|
{
|
|
U32 index = iter.mData.mObj.mIndex;
|
|
SimSet* set = iter.mData.mObj.mSet;
|
|
|
|
if( index >= set->size() )
|
|
{
|
|
ip = breakIp;
|
|
continue;
|
|
}
|
|
|
|
iter.mVariable->setIntValue( set->at( index )->getId() );
|
|
iter.mData.mObj.mIndex = index + 1;
|
|
}
|
|
|
|
++ ip;
|
|
break;
|
|
}
|
|
|
|
case OP_ITER_END:
|
|
{
|
|
-- _ITER;
|
|
-- iterDepth;
|
|
|
|
STR.rewind();
|
|
|
|
iterStack[ _ITER ].mIsStringIter = false;
|
|
break;
|
|
}
|
|
|
|
case OP_INVALID:
|
|
|
|
default:
|
|
// error!
|
|
goto execFinished;
|
|
}
|
|
}
|
|
execFinished:
|
|
|
|
if ( telDebuggerOn && setFrame < 0 )
|
|
TelDebugger->popStackFrame();
|
|
|
|
if ( popFrame )
|
|
gEvalState.popFrame();
|
|
|
|
if(argv)
|
|
{
|
|
if(gEvalState.traceOn)
|
|
{
|
|
traceBuffer[0] = 0;
|
|
dStrcat(traceBuffer, "Leaving ");
|
|
|
|
if(packageName)
|
|
{
|
|
dStrcat(traceBuffer, "[");
|
|
dStrcat(traceBuffer, packageName);
|
|
dStrcat(traceBuffer, "]");
|
|
}
|
|
if(thisNamespace && thisNamespace->mName)
|
|
{
|
|
dSprintf(traceBuffer + dStrlen(traceBuffer), sizeof(traceBuffer) - dStrlen(traceBuffer),
|
|
"%s::%s() - return %s", thisNamespace->mName, thisFunctionName, STR.getStringValue());
|
|
}
|
|
else
|
|
{
|
|
dSprintf(traceBuffer + dStrlen(traceBuffer), sizeof(traceBuffer) - dStrlen(traceBuffer),
|
|
"%s() - return %s", thisFunctionName, STR.getStringValue());
|
|
}
|
|
Con::printf("%s", traceBuffer);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
delete[] globalStrings;
|
|
globalStringsMaxLen = 0;
|
|
|
|
delete[] globalFloats;
|
|
globalStrings = NULL;
|
|
globalFloats = NULL;
|
|
}
|
|
smCurrentCodeBlock = saveCodeBlock;
|
|
if(saveCodeBlock && saveCodeBlock->name)
|
|
{
|
|
Con::gCurrentFile = saveCodeBlock->name;
|
|
Con::gCurrentRoot = saveCodeBlock->modPath;
|
|
}
|
|
|
|
decRefCount();
|
|
|
|
#ifdef TORQUE_DEBUG
|
|
AssertFatal(!(STR.mStartStackSize > stackStart), "String stack not popped enough in script exec");
|
|
AssertFatal(!(STR.mStartStackSize < stackStart), "String stack popped too much in script exec");
|
|
#endif
|
|
|
|
return returnValue;
|
|
}
|
|
|
|
//------------------------------------------------------------
|