mirror of
https://github.com/TorqueGameEngines/Torque3D.git
synced 2026-01-20 04:34:48 +00:00
2353 lines
70 KiB
C++
2353 lines
70 KiB
C++
//-----------------------------------------------------------------------------
|
|
// Copyright (c) 2013 GarageGames, LLC
|
|
// Copyright (c) 2015 Faust Logic, Inc.
|
|
// Copyright (c) 2021 TGEMIT Authors & Contributors
|
|
//
|
|
// 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"
|
|
|
|
#include "console/returnBuffer.h"
|
|
#include "console/consoleValueStack.h"
|
|
|
|
#ifndef TORQUE_TGB_ONLY
|
|
#include "materials/materialDefinition.h"
|
|
#include "materials/materialManager.h"
|
|
#endif
|
|
|
|
using namespace Compiler;
|
|
|
|
enum EvalConstants
|
|
{
|
|
MaxStackSize = 1024,
|
|
FieldBufferSizeString = 2048,
|
|
FieldBufferSizeNumeric = 128,
|
|
ConcatBufferInitialSize = 8192,
|
|
MethodOnComponent = -2
|
|
};
|
|
|
|
/// 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;
|
|
|
|
/// True if the variable referenced is a global
|
|
bool mIsGlobalVariable;
|
|
|
|
union
|
|
{
|
|
|
|
/// The iterator variable if we are a global variable
|
|
Dictionary::Entry* mVariable;
|
|
|
|
/// The register variable if we are a local variable
|
|
S32 mRegister;
|
|
} mVar;
|
|
|
|
/// 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;
|
|
};
|
|
|
|
ConsoleValueStack<4096> gCallStack;
|
|
|
|
StringStack STR;
|
|
|
|
IterStackRecord iterStack[MaxStackSize];
|
|
U32 _ITER = 0; ///< Stack pointer for iterStack.
|
|
|
|
ConsoleValue stack[MaxStackSize];
|
|
S32 _STK = 0;
|
|
|
|
const char* tsconcat(const char* strA, const char* strB, S32& outputLen)
|
|
{
|
|
S32 lenA = dStrlen(strA);
|
|
S32 lenB = dStrlen(strB);
|
|
|
|
S32 len = lenA + lenB + 1;
|
|
|
|
char* concatBuffer = (char*)dMalloc(len);
|
|
|
|
concatBuffer[len - 1] = '\0';
|
|
memcpy(concatBuffer, strA, lenA);
|
|
memcpy(concatBuffer + lenA, strB, lenB);
|
|
|
|
outputLen = lenA + lenB;
|
|
return concatBuffer;
|
|
}
|
|
|
|
namespace Con
|
|
{
|
|
// Current script file name and root, these are registered as
|
|
// console variables.
|
|
extern StringTableEntry gCurrentFile;
|
|
extern StringTableEntry gCurrentRoot;
|
|
extern S32 gObjectCopyFailures;
|
|
}
|
|
|
|
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, size);
|
|
if (walk->mParent)
|
|
dStrcat(ret, " -> ", size);
|
|
}
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
static void getFieldComponent(SimObject* object, StringTableEntry field, const char* array, StringTableEntry subField, char val[], S32 currentLocalRegister)
|
|
{
|
|
const char* prevVal = NULL;
|
|
|
|
if (object && field)
|
|
prevVal = object->getDataField(field, array);
|
|
else if (currentLocalRegister != -1)
|
|
prevVal = gEvalState.getLocalStringVariable(currentLocalRegister);
|
|
else if (gEvalState.currentVariable)
|
|
prevVal = gEvalState.getStringVariable();
|
|
|
|
// 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"), 128);
|
|
|
|
else if (subField == xyzw[1] || subField == rgba[1])
|
|
dStrcpy(val, StringUnit::getUnit(prevVal, 1, " \t\n"), 128);
|
|
|
|
else if (subField == xyzw[2] || subField == rgba[2])
|
|
dStrcpy(val, StringUnit::getUnit(prevVal, 2, " \t\n"), 128);
|
|
|
|
else if (subField == xyzw[3] || subField == rgba[3])
|
|
dStrcpy(val, StringUnit::getUnit(prevVal, 3, " \t\n"), 128);
|
|
|
|
else
|
|
val[0] = 0;
|
|
}
|
|
else
|
|
val[0] = 0;
|
|
}
|
|
|
|
static void setFieldComponent(SimObject* object, StringTableEntry field, const char* array, StringTableEntry subField, S32 currentLocalRegister)
|
|
{
|
|
// Copy the current string value
|
|
char strValue[1024];
|
|
dStrncpy(strValue, stack[_STK].getString(), 1024);
|
|
|
|
char val[1024] = "";
|
|
const char* prevVal = NULL;
|
|
|
|
if (object && field)
|
|
prevVal = object->getDataField(field, array);
|
|
else if (currentLocalRegister != -1)
|
|
prevVal = gEvalState.getLocalStringVariable(currentLocalRegister);
|
|
// 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"), 128);
|
|
|
|
else if (subField == xyzw[1] || subField == rgba[1])
|
|
dStrcpy(val, StringUnit::setUnit(prevVal, 1, strValue, " \t\n"), 128);
|
|
|
|
else if (subField == xyzw[2] || subField == rgba[2])
|
|
dStrcpy(val, StringUnit::setUnit(prevVal, 2, strValue, " \t\n"), 128);
|
|
|
|
else if (subField == xyzw[3] || subField == rgba[3])
|
|
dStrcpy(val, StringUnit::setUnit(prevVal, 3, strValue, " \t\n"), 128);
|
|
|
|
if (val[0] != 0)
|
|
{
|
|
// Update the field or variable.
|
|
if (object && field)
|
|
object->setDataField(field, 0, val);
|
|
else if (currentLocalRegister != -1)
|
|
gEvalState.setLocalStringVariable(currentLocalRegister, val, dStrlen(val));
|
|
else if (gEvalState.currentVariable)
|
|
gEvalState.setStringVariable(val);
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------
|
|
|
|
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
|
|
{
|
|
ReturnBuffer retBuffer;
|
|
|
|
char *getReturnBuffer(U32 bufferSize)
|
|
{
|
|
return retBuffer.getBuffer(bufferSize);
|
|
}
|
|
|
|
char *getReturnBuffer(const char *stringToCopy)
|
|
{
|
|
U32 len = dStrlen(stringToCopy) + 1;
|
|
char *ret = retBuffer.getBuffer(len);
|
|
dMemcpy(ret, stringToCopy, len);
|
|
return ret;
|
|
}
|
|
|
|
char* getReturnBuffer(const String& str)
|
|
{
|
|
const U32 size = str.size();
|
|
char* ret = retBuffer.getBuffer(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);
|
|
}
|
|
|
|
char *getFloatArg(F64 arg)
|
|
{
|
|
char *ret = STR.getArgBuffer(32);
|
|
dSprintf(ret, 32, "%g", arg);
|
|
return ret;
|
|
}
|
|
|
|
char *getIntArg(S32 arg)
|
|
{
|
|
char *ret = STR.getArgBuffer(32);
|
|
dSprintf(ret, 32, "%d", arg);
|
|
return ret;
|
|
}
|
|
|
|
char* getBoolArg(bool arg)
|
|
{
|
|
char *ret = STR.getArgBuffer(32);
|
|
dSprintf(ret, 32, "%d", arg);
|
|
return ret;
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------
|
|
|
|
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);
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------
|
|
|
|
S32 ExprEvalState::getIntVariable()
|
|
{
|
|
return currentVariable ? currentVariable->getIntValue() : 0;
|
|
}
|
|
|
|
F64 ExprEvalState::getFloatVariable()
|
|
{
|
|
return currentVariable ? currentVariable->getFloatValue() : 0;
|
|
}
|
|
|
|
const char *ExprEvalState::getStringVariable()
|
|
{
|
|
return currentVariable ? currentVariable->getStringValue() : "";
|
|
}
|
|
|
|
//------------------------------------------------------------
|
|
|
|
void ExprEvalState::setIntVariable(S32 val)
|
|
{
|
|
AssertFatal(currentVariable != NULL, "Invalid evaluator state - trying to set null variable!");
|
|
currentVariable->setIntValue(val);
|
|
}
|
|
|
|
void ExprEvalState::setFloatVariable(F64 val)
|
|
{
|
|
AssertFatal(currentVariable != NULL, "Invalid evaluator state - trying to set null variable!");
|
|
currentVariable->setFloatValue(val);
|
|
}
|
|
|
|
void ExprEvalState::setStringVariable(const char *val)
|
|
{
|
|
AssertFatal(currentVariable != NULL, "Invalid evaluator state - trying to set null variable!");
|
|
currentVariable->setStringValue(val);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
enum class FloatOperation
|
|
{
|
|
Add,
|
|
Sub,
|
|
Mul,
|
|
Div,
|
|
|
|
LT,
|
|
LE,
|
|
GR,
|
|
GE,
|
|
EQ,
|
|
NE
|
|
};
|
|
|
|
template<FloatOperation Op>
|
|
TORQUE_NOINLINE void doSlowMathOp()
|
|
{
|
|
ConsoleValue& a = stack[_STK];
|
|
ConsoleValue& b = stack[_STK - 1];
|
|
|
|
// Arithmetic
|
|
if constexpr (Op == FloatOperation::Add)
|
|
stack[_STK - 1].setFloat(a.getFloat() + b.getFloat());
|
|
else if constexpr (Op == FloatOperation::Sub)
|
|
stack[_STK - 1].setFloat(a.getFloat() - b.getFloat());
|
|
else if constexpr (Op == FloatOperation::Mul)
|
|
stack[_STK - 1].setFloat(a.getFloat() * b.getFloat());
|
|
else if constexpr (Op == FloatOperation::Div)
|
|
stack[_STK - 1].setFloat(a.getFloat() / b.getFloat());
|
|
|
|
// Logical
|
|
if constexpr (Op == FloatOperation::LT)
|
|
stack[_STK - 1].setInt(a.getFloat() < b.getFloat());
|
|
if constexpr (Op == FloatOperation::LE)
|
|
stack[_STK - 1].setInt(a.getFloat() <= b.getFloat());
|
|
if constexpr (Op == FloatOperation::GR)
|
|
stack[_STK - 1].setInt(a.getFloat() > b.getFloat());
|
|
if constexpr (Op == FloatOperation::GE)
|
|
stack[_STK - 1].setInt(a.getFloat() >= b.getFloat());
|
|
if constexpr (Op == FloatOperation::EQ)
|
|
stack[_STK - 1].setInt(a.getFloat() == b.getFloat());
|
|
if constexpr (Op == FloatOperation::NE)
|
|
stack[_STK - 1].setInt(a.getFloat() != b.getFloat());
|
|
|
|
_STK--;
|
|
}
|
|
|
|
template<FloatOperation Op>
|
|
TORQUE_FORCEINLINE void doFloatMathOperation()
|
|
{
|
|
ConsoleValue& a = stack[_STK];
|
|
ConsoleValue& b = stack[_STK - 1];
|
|
|
|
S32 fastIf = (a.getType() == ConsoleValueType::cvFloat) & (b.getType() == ConsoleValueType::cvFloat);
|
|
if (fastIf)
|
|
{
|
|
// Arithmetic
|
|
if constexpr (Op == FloatOperation::Add)
|
|
stack[_STK - 1].setFastFloat(a.getFastFloat() + b.getFastFloat());
|
|
if constexpr (Op == FloatOperation::Sub)
|
|
stack[_STK - 1].setFastFloat(a.getFastFloat() - b.getFastFloat());
|
|
if constexpr (Op == FloatOperation::Mul)
|
|
stack[_STK - 1].setFastFloat(a.getFastFloat() * b.getFastFloat());
|
|
if constexpr (Op == FloatOperation::Div)
|
|
stack[_STK - 1].setFastFloat(a.getFastFloat() / b.getFastFloat());
|
|
|
|
// Logical
|
|
if constexpr (Op == FloatOperation::LT)
|
|
stack[_STK - 1].setFastInt(a.getFastFloat() < b.getFastFloat());
|
|
if constexpr (Op == FloatOperation::LE)
|
|
stack[_STK - 1].setFastInt(a.getFastFloat() <= b.getFastFloat());
|
|
if constexpr (Op == FloatOperation::GR)
|
|
stack[_STK - 1].setFastInt(a.getFastFloat() > b.getFastFloat());
|
|
if constexpr (Op == FloatOperation::GE)
|
|
stack[_STK - 1].setFastInt(a.getFastFloat() >= b.getFastFloat());
|
|
if constexpr (Op == FloatOperation::EQ)
|
|
stack[_STK - 1].setFastInt(a.getFastFloat() == b.getFastFloat());
|
|
if constexpr (Op == FloatOperation::NE)
|
|
stack[_STK - 1].setFastInt(a.getFastFloat() != b.getFastFloat());
|
|
|
|
_STK--;
|
|
}
|
|
else
|
|
{
|
|
doSlowMathOp<Op>();
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
enum class IntegerOperation
|
|
{
|
|
BitAnd,
|
|
BitOr,
|
|
Xor,
|
|
LShift,
|
|
RShift,
|
|
|
|
LogicalAnd,
|
|
LogicalOr
|
|
};
|
|
|
|
template<IntegerOperation Op>
|
|
TORQUE_NOINLINE void doSlowIntegerOp()
|
|
{
|
|
ConsoleValue& a = stack[_STK];
|
|
ConsoleValue& b = stack[_STK - 1];
|
|
|
|
// Bitwise Op
|
|
if constexpr (Op == IntegerOperation::BitAnd)
|
|
stack[_STK - 1].setInt(a.getInt() & b.getInt());
|
|
if constexpr (Op == IntegerOperation::BitOr)
|
|
stack[_STK - 1].setInt(a.getInt() | b.getInt());
|
|
if constexpr (Op == IntegerOperation::Xor)
|
|
stack[_STK - 1].setInt(a.getInt() ^ b.getInt());
|
|
if constexpr (Op == IntegerOperation::LShift)
|
|
stack[_STK - 1].setInt(a.getInt() << b.getInt());
|
|
if constexpr (Op == IntegerOperation::RShift)
|
|
stack[_STK - 1].setInt(a.getInt() >> b.getInt());
|
|
|
|
// Logical Op
|
|
if constexpr (Op == IntegerOperation::LogicalAnd)
|
|
stack[_STK - 1].setBool(a.getInt() && b.getInt());
|
|
if constexpr (Op == IntegerOperation::LogicalOr)
|
|
stack[_STK - 1].setBool(a.getInt() || b.getInt());
|
|
|
|
_STK--;
|
|
}
|
|
|
|
template<IntegerOperation Op>
|
|
TORQUE_FORCEINLINE void doIntOperation()
|
|
{
|
|
ConsoleValue& a = stack[_STK];
|
|
ConsoleValue& b = stack[_STK - 1];
|
|
|
|
if (a.isNumberType() && b.isNumberType())
|
|
{
|
|
// Bitwise Op
|
|
if constexpr (Op == IntegerOperation::BitAnd)
|
|
stack[_STK - 1].setFastInt(a.getFastInt() & b.getFastInt());
|
|
if constexpr (Op == IntegerOperation::BitOr)
|
|
stack[_STK - 1].setFastInt(a.getFastInt() | b.getFastInt());
|
|
if constexpr (Op == IntegerOperation::Xor)
|
|
stack[_STK - 1].setFastInt(a.getFastInt() ^ b.getFastInt());
|
|
if constexpr (Op == IntegerOperation::LShift)
|
|
stack[_STK - 1].setFastInt(a.getFastInt() << b.getFastInt());
|
|
if constexpr (Op == IntegerOperation::RShift)
|
|
stack[_STK - 1].setFastInt(a.getFastInt() >> b.getFastInt());
|
|
|
|
// Logical Op
|
|
if constexpr (Op == IntegerOperation::LogicalAnd)
|
|
stack[_STK - 1].setBool(a.getFastInt() && b.getFastInt());
|
|
if constexpr (Op == IntegerOperation::LogicalOr)
|
|
stack[_STK - 1].setBool(a.getFastInt() || b.getFastInt());
|
|
|
|
_STK--;
|
|
}
|
|
else
|
|
{
|
|
doSlowIntegerOp<Op>();
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
U32 gExecCount = 0;
|
|
ConsoleValue CodeBlock::exec(U32 ip, const char* functionName, Namespace* thisNamespace, U32 argc, ConsoleValue* argv, bool noCalls, StringTableEntry packageName, S32 setFrame)
|
|
{
|
|
#ifdef TORQUE_DEBUG
|
|
U32 stackStart = _STK;
|
|
gExecCount++;
|
|
#endif
|
|
|
|
const U32 TRACE_BUFFER_SIZE = 1024;
|
|
static char traceBuffer[TRACE_BUFFER_SIZE];
|
|
U32 i;
|
|
|
|
U32 iterDepth = 0;
|
|
ConsoleValue returnValue;
|
|
|
|
incRefCount();
|
|
F64* curFloatTable;
|
|
char* curStringTable;
|
|
S32 curStringTableLen = 0; //clint to ensure we dont overwrite it
|
|
|
|
StringTableEntry thisFunctionName = NULL;
|
|
bool popFrame = false;
|
|
if (argv)
|
|
{
|
|
// assume this points into a function decl:
|
|
U32 fnArgc = code[ip + 2 + 6];
|
|
U32 regCount = code[ip + 2 + 7];
|
|
thisFunctionName = CodeToSTE(code, ip);
|
|
S32 wantedArgc = getMin(argc - 1, fnArgc); // argv[0] is func name
|
|
if (gEvalState.traceOn)
|
|
{
|
|
traceBuffer[0] = 0;
|
|
dStrcat(traceBuffer, "Entering ", TRACE_BUFFER_SIZE);
|
|
if (packageName)
|
|
{
|
|
dStrcat(traceBuffer, "[", TRACE_BUFFER_SIZE);
|
|
dStrcat(traceBuffer, packageName, TRACE_BUFFER_SIZE);
|
|
dStrcat(traceBuffer, "]", TRACE_BUFFER_SIZE);
|
|
}
|
|
if (thisNamespace && thisNamespace->mName)
|
|
{
|
|
dSprintf(traceBuffer + (U32)dStrlen(traceBuffer), sizeof(traceBuffer) - (U32)dStrlen(traceBuffer),
|
|
"%s::%s(", thisNamespace->mName, thisFunctionName);
|
|
}
|
|
else
|
|
{
|
|
dSprintf(traceBuffer + (U32)dStrlen(traceBuffer), sizeof(traceBuffer) - (U32)dStrlen(traceBuffer),
|
|
"%s(", thisFunctionName);
|
|
}
|
|
for (i = 0; i < wantedArgc; i++)
|
|
{
|
|
dStrcat(traceBuffer, argv[i + 1].getString(), TRACE_BUFFER_SIZE);
|
|
if (i != wantedArgc - 1)
|
|
dStrcat(traceBuffer, ", ", TRACE_BUFFER_SIZE);
|
|
}
|
|
dStrcat(traceBuffer, ")", TRACE_BUFFER_SIZE);
|
|
Con::printf("%s", traceBuffer);
|
|
}
|
|
gEvalState.pushFrame(thisFunctionName, thisNamespace, regCount);
|
|
popFrame = true;
|
|
for (i = 0; i < wantedArgc; i++)
|
|
{
|
|
S32 reg = code[ip + (2 + 6 + 1 + 1) + i];
|
|
ConsoleValue& value = argv[i + 1];
|
|
gEvalState.moveConsoleValue(reg, std::move(value));
|
|
}
|
|
ip = ip + fnArgc + (2 + 6 + 1 + 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?
|
|
// compiling a file will force setFrame to 0, forcing us to get a new frame.
|
|
if (setFrame <= 0)
|
|
{
|
|
// argc is the local count for eval
|
|
gEvalState.pushFrame(NULL, NULL, argc);
|
|
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.getTopOfStack() - setFrame - 1;
|
|
gEvalState.pushFrameRef(stackIndex);
|
|
popFrame = true;
|
|
}
|
|
}
|
|
|
|
// 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;
|
|
U32 failJump = 0;
|
|
StringTableEntry fnName;
|
|
StringTableEntry fnNamespace, fnPackage;
|
|
|
|
static const U32 objectCreationStackSize = 32;
|
|
U32 objectCreationStackIndex = 0;
|
|
struct {
|
|
SimObject* newObject;
|
|
U32 failJump;
|
|
} objectCreationStack[objectCreationStackSize] = {};
|
|
|
|
SimObject* currentNewObject = 0;
|
|
StringTableEntry prevField = NULL;
|
|
StringTableEntry curField = NULL;
|
|
SimObject* prevObject = NULL;
|
|
SimObject* curObject = NULL;
|
|
SimObject* saveObject = NULL;
|
|
Namespace::Entry* nsEntry;
|
|
Namespace* ns = NULL;
|
|
const char* curFNDocBlock = NULL;
|
|
const char* curNSDocBlock = NULL;
|
|
const S32 nsDocLength = 128;
|
|
char nsDocBlockClass[nsDocLength];
|
|
|
|
S32 callArgc;
|
|
ConsoleValue* 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;
|
|
S32 reg;
|
|
S32 currentRegister = -1;
|
|
|
|
// 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++];
|
|
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;
|
|
|
|
Namespace::unlinkPackages();
|
|
if (fnNamespace == NULL && fnPackage == NULL)
|
|
ns = Namespace::global();
|
|
else
|
|
ns = Namespace::find(fnNamespace, fnPackage);
|
|
ns->addFunction(fnName, this, hasBody ? ip : 0);// 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.
|
|
gCallStack.argvc(NULL, callArgc, &callArgv);
|
|
AssertFatal(callArgc - 3 >= 0, avar("Call Arg needs at least 3, only has %d", callArgc));
|
|
const char* objectName = callArgv[2].getString();
|
|
|
|
// 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].getString()))
|
|
{
|
|
Con::errorf(ConsoleLogEntry::General, "Cannot re-declare data block %s with a different class.", objectName);
|
|
ip = failJump;
|
|
gCallStack.popFrame();
|
|
break;
|
|
}
|
|
|
|
// If there was one, set the currentNewObject and move on.
|
|
if (db)
|
|
currentNewObject = db;
|
|
}
|
|
else if (!isInternal)
|
|
{
|
|
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;
|
|
gCallStack.popFrame();
|
|
break;
|
|
}
|
|
|
|
SimObject* obj = Sim::findObject((const char*)objectName);
|
|
if (obj)
|
|
{
|
|
if (isSingleton)
|
|
{
|
|
// Make sure we're not trying to change types
|
|
if (dStricmp(obj->getClassName(), callArgv[1].getString()) != 0)
|
|
{
|
|
Con::errorf(ConsoleLogEntry::General, "%s: Cannot re-declare object [%s] with a different class [%s] - was [%s].",
|
|
getFileLine(ip), objectName, callArgv[1].getString(), obj->getClassName());
|
|
ip = failJump;
|
|
gCallStack.popFrame();
|
|
break;
|
|
}
|
|
|
|
// We're creating a singleton, so use the found object instead of creating a new object.
|
|
currentNewObject = obj;
|
|
Con::warnf("%s: Singleton Object was already created with name %s. Using existing object.",
|
|
getFileLine(ip), objectName);
|
|
}
|
|
}
|
|
}
|
|
|
|
gCallStack.popFrame();
|
|
|
|
if (!currentNewObject)
|
|
{
|
|
// Well, looks like we have to create a new object.
|
|
ConsoleObject* object = ConsoleObject::create(callArgv[1].getString());
|
|
|
|
// Deal with failure!
|
|
if (!object)
|
|
{
|
|
Con::errorf(ConsoleLogEntry::General, "%s: Unable to instantiate non-conobject class %s.", getFileLine(ip - 1), callArgv[1].getString());
|
|
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 - 1), callArgv[1].getString());
|
|
|
|
// Clean up...
|
|
delete object;
|
|
currentNewObject = NULL;
|
|
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 - 1), callArgv[1].getString());
|
|
delete object;
|
|
ip = failJump;
|
|
break;
|
|
}
|
|
|
|
// Set the declaration line
|
|
currentNewObject->setDeclarationLine(lineNumber);
|
|
|
|
// Set the file that this object was created in
|
|
currentNewObject->setFilename(this->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);
|
|
|
|
// copy any substitution statements
|
|
SimDataBlock* parent_db = dynamic_cast<SimDataBlock*>(parent);
|
|
if (parent_db)
|
|
{
|
|
SimDataBlock* currentNewObject_db = dynamic_cast<SimDataBlock*>(currentNewObject);
|
|
if (currentNewObject_db)
|
|
currentNewObject_db->copySubstitutionsFrom(parent_db);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (Con::gObjectCopyFailures == -1)
|
|
Con::errorf(ConsoleLogEntry::General, "%s: Unable to find parent object %s for %s.", getFileLine(ip - 1), objParent, callArgv[1].getString());
|
|
else
|
|
++Con::gObjectCopyFailures;
|
|
|
|
delete object;
|
|
currentNewObject = NULL;
|
|
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 );
|
|
}
|
|
|
|
// Do the constructor parameters.
|
|
if (!currentNewObject->processArguments(callArgc - 3, callArgv + 3))
|
|
{
|
|
delete currentNewObject;
|
|
currentNewObject = NULL;
|
|
ip = failJump;
|
|
break;
|
|
}
|
|
|
|
// If it's not a datablock, allow people to modify bits of it.
|
|
if (!isDataBlock)
|
|
{
|
|
currentNewObject->setModStaticFields(true);
|
|
currentNewObject->setModDynamicFields(true);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
currentNewObject->reloadReset(); // AFX (reload-reset)
|
|
|
|
// 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());
|
|
|
|
// temporarily block name change
|
|
SimObject::preventNameChanging = true;
|
|
currentNewObject->setCopySource(parent);
|
|
currentNewObject->assignFieldsFrom(parent);
|
|
// restore name changing
|
|
SimObject::preventNameChanging = false;
|
|
|
|
// copy any substitution statements
|
|
SimDataBlock* parent_db = dynamic_cast<SimDataBlock*>(parent);
|
|
if (parent_db)
|
|
{
|
|
SimDataBlock* currentNewObject_db = dynamic_cast<SimDataBlock*>(currentNewObject);
|
|
if (currentNewObject_db)
|
|
currentNewObject_db->copySubstitutionsFrom(parent_db);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Con::errorf(ConsoleLogEntry::General, "%d: Unable to find parent object %s for %s.", lineNumber, objParent, callArgv[1].getString());
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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());
|
|
|
|
// Make sure it wasn't already added, then add it.
|
|
if (currentNewObject == NULL)
|
|
{
|
|
break;
|
|
}
|
|
|
|
bool isMessage = dynamic_cast<Message*>(currentNewObject) != NULL;
|
|
|
|
if (currentNewObject->isProperlyAdded() == false)
|
|
{
|
|
bool ret = false;
|
|
if (isMessage)
|
|
{
|
|
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 - 2), currentNewObject->getName(), currentNewObject->getClassName());
|
|
delete currentNewObject;
|
|
currentNewObject = NULL;
|
|
ip = failJump;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Are we dealing with a datablock?
|
|
SimDataBlock* dataBlock = dynamic_cast<SimDataBlock*>(currentNewObject);
|
|
String errorStr;
|
|
|
|
// If so, preload it.
|
|
if (dataBlock && !dataBlock->preload(true, errorStr))
|
|
{
|
|
Con::errorf(ConsoleLogEntry::General, "%s: preload failed for %s: %s.", getFileLine(ip - 2),
|
|
currentNewObject->getName(), errorStr.c_str());
|
|
dataBlock->deleteObject();
|
|
currentNewObject = NULL;
|
|
ip = failJump;
|
|
break;
|
|
}
|
|
|
|
// What group will we be added to, if any?
|
|
U32 groupAddId = (U32)stack[_STK].getInt();
|
|
SimGroup* grp = NULL;
|
|
SimSet* set = 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() == NULL)
|
|
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)
|
|
S32 id = currentNewObject->getId();
|
|
if (placeAtRoot)
|
|
stack[_STK].setInt(id);
|
|
else
|
|
stack[++_STK].setInt(id);
|
|
|
|
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)
|
|
_STK--;
|
|
break;
|
|
}
|
|
|
|
case OP_FINISH_OBJECT:
|
|
{
|
|
if (currentNewObject)
|
|
currentNewObject->onPostAdd();
|
|
|
|
AssertFatal( objectCreationStackIndex >= 0, "Object Stack is empty." );
|
|
// Restore the object info from the stack [7/9/2007 Black]
|
|
currentNewObject = objectCreationStack[--objectCreationStackIndex].newObject;
|
|
failJump = objectCreationStack[objectCreationStackIndex].failJump;
|
|
break;
|
|
}
|
|
|
|
case OP_JMPIFFNOT:
|
|
if (stack[_STK--].getFloat())
|
|
{
|
|
ip++;
|
|
break;
|
|
}
|
|
ip = code[ip];
|
|
break;
|
|
case OP_JMPIFNOT:
|
|
if (stack[_STK--].getInt())
|
|
{
|
|
ip++;
|
|
break;
|
|
}
|
|
ip = code[ip];
|
|
break;
|
|
case OP_JMPIFF:
|
|
if (!stack[_STK--].getFloat())
|
|
{
|
|
ip++;
|
|
break;
|
|
}
|
|
ip = code[ip];
|
|
break;
|
|
case OP_JMPIF:
|
|
if (!stack[_STK--].getFloat())
|
|
{
|
|
ip++;
|
|
break;
|
|
}
|
|
ip = code[ip];
|
|
break;
|
|
case OP_JMPIFNOT_NP:
|
|
if (stack[_STK].getInt())
|
|
{
|
|
_STK--;
|
|
ip++;
|
|
break;
|
|
}
|
|
ip = code[ip];
|
|
break;
|
|
case OP_JMPIF_NP:
|
|
if (!stack[_STK].getInt())
|
|
{
|
|
_STK--;
|
|
ip++;
|
|
break;
|
|
}
|
|
ip = code[ip];
|
|
break;
|
|
case OP_JMP:
|
|
ip = code[ip];
|
|
break;
|
|
|
|
case OP_RETURN_VOID:
|
|
{
|
|
if (iterDepth > 0)
|
|
{
|
|
// Clear iterator state.
|
|
while (iterDepth > 0)
|
|
{
|
|
iterStack[--_ITER].mIsStringIter = false;
|
|
--iterDepth;
|
|
|
|
_STK--; // this is a pop from foreach()
|
|
}
|
|
}
|
|
|
|
returnValue.setEmptyString();
|
|
|
|
goto execFinished;
|
|
}
|
|
|
|
case OP_RETURN:
|
|
{
|
|
returnValue = std::move(stack[_STK]);
|
|
_STK--;
|
|
|
|
// Clear iterator state.
|
|
while (iterDepth > 0)
|
|
{
|
|
iterStack[--_ITER].mIsStringIter = false;
|
|
--iterDepth;
|
|
|
|
_STK--;
|
|
}
|
|
|
|
goto execFinished;
|
|
}
|
|
case OP_RETURN_FLT:
|
|
returnValue.setFloat(stack[_STK].getFloat());
|
|
_STK--;
|
|
|
|
// Clear iterator state.
|
|
while (iterDepth > 0)
|
|
{
|
|
iterStack[--_ITER].mIsStringIter = false;
|
|
--iterDepth;
|
|
|
|
_STK--;
|
|
}
|
|
|
|
goto execFinished;
|
|
|
|
case OP_RETURN_UINT:
|
|
returnValue.setInt(stack[_STK].getInt());
|
|
_STK--;
|
|
|
|
// Clear iterator state.
|
|
while (iterDepth > 0)
|
|
{
|
|
iterStack[--_ITER].mIsStringIter = false;
|
|
--iterDepth;
|
|
|
|
_STK--;
|
|
}
|
|
|
|
goto execFinished;
|
|
|
|
case OP_CMPEQ:
|
|
doFloatMathOperation<FloatOperation::EQ>();
|
|
break;
|
|
|
|
case OP_CMPGR:
|
|
doFloatMathOperation<FloatOperation::GR>();
|
|
break;
|
|
|
|
case OP_CMPGE:
|
|
doFloatMathOperation<FloatOperation::GE>();
|
|
break;
|
|
|
|
case OP_CMPLT:
|
|
doFloatMathOperation<FloatOperation::LT>();
|
|
break;
|
|
|
|
case OP_CMPLE:
|
|
doFloatMathOperation<FloatOperation::LE>();
|
|
break;
|
|
|
|
case OP_CMPNE:
|
|
doFloatMathOperation<FloatOperation::NE>();
|
|
break;
|
|
|
|
case OP_XOR:
|
|
doIntOperation<IntegerOperation::Xor>();
|
|
break;
|
|
|
|
case OP_BITAND:
|
|
doIntOperation<IntegerOperation::BitAnd>();
|
|
break;
|
|
|
|
case OP_BITOR:
|
|
doIntOperation<IntegerOperation::BitOr>();
|
|
break;
|
|
|
|
case OP_NOT:
|
|
stack[_STK].setBool(!stack[_STK].getInt());
|
|
break;
|
|
|
|
case OP_NOTF:
|
|
stack[_STK].setInt(!stack[_STK].getFloat());
|
|
break;
|
|
|
|
case OP_ONESCOMPLEMENT:
|
|
stack[_STK].setInt(~stack[_STK].getInt());
|
|
break;
|
|
|
|
case OP_SHR:
|
|
doIntOperation<IntegerOperation::RShift>();
|
|
break;
|
|
|
|
case OP_SHL:
|
|
doIntOperation<IntegerOperation::LShift>();
|
|
break;
|
|
|
|
case OP_AND:
|
|
doIntOperation<IntegerOperation::LogicalAnd>();
|
|
break;
|
|
|
|
case OP_OR:
|
|
doIntOperation<IntegerOperation::LogicalOr>();
|
|
break;
|
|
|
|
case OP_ADD:
|
|
doFloatMathOperation<FloatOperation::Add>();
|
|
break;
|
|
|
|
case OP_SUB:
|
|
doFloatMathOperation<FloatOperation::Sub>();
|
|
break;
|
|
|
|
case OP_MUL:
|
|
doFloatMathOperation<FloatOperation::Mul>();
|
|
break;
|
|
|
|
case OP_DIV:
|
|
doFloatMathOperation<FloatOperation::Div>();
|
|
break;
|
|
|
|
case OP_MOD:
|
|
{
|
|
S64 divisor = stack[_STK - 1].getInt();
|
|
if (divisor != 0)
|
|
stack[_STK - 1].setInt(stack[_STK].getInt() % divisor);
|
|
else
|
|
stack[_STK - 1].setInt(0);
|
|
_STK--;
|
|
break;
|
|
}
|
|
|
|
case OP_NEG:
|
|
stack[_STK].setFloat(-stack[_STK].getFloat());
|
|
break;
|
|
|
|
case OP_INC:
|
|
reg = code[ip++];
|
|
currentRegister = reg;
|
|
gEvalState.setLocalFloatVariable(reg, gEvalState.getLocalFloatVariable(reg) + 1.0);
|
|
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;
|
|
|
|
// Used for local variable caching of what is active...when we
|
|
// set a global, we aren't active
|
|
currentRegister = -1;
|
|
|
|
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;
|
|
|
|
// Used for local variable caching of what is active...when we
|
|
// set a global, we aren't active
|
|
currentRegister = -1;
|
|
|
|
gEvalState.setCurVarNameCreate(var);
|
|
|
|
// See OP_SETCURVAR for why we do this.
|
|
curFNDocBlock = NULL;
|
|
curNSDocBlock = NULL;
|
|
break;
|
|
|
|
case OP_SETCURVAR_ARRAY:
|
|
var = StringTable->insert(stack[_STK].getString());
|
|
|
|
// See OP_SETCURVAR
|
|
prevField = NULL;
|
|
prevObject = NULL;
|
|
curObject = NULL;
|
|
|
|
// Used for local variable caching of what is active...when we
|
|
// set a global, we aren't active
|
|
currentRegister = -1;
|
|
|
|
gEvalState.setCurVarName(var);
|
|
|
|
// See OP_SETCURVAR for why we do this.
|
|
curFNDocBlock = NULL;
|
|
curNSDocBlock = NULL;
|
|
break;
|
|
|
|
case OP_SETCURVAR_ARRAY_CREATE:
|
|
var = StringTable->insert(stack[_STK].getString());
|
|
|
|
// See OP_SETCURVAR
|
|
prevField = NULL;
|
|
prevObject = NULL;
|
|
curObject = NULL;
|
|
|
|
// Used for local variable caching of what is active...when we
|
|
// set a global, we aren't active
|
|
currentRegister = -1;
|
|
|
|
gEvalState.setCurVarNameCreate(var);
|
|
|
|
// See OP_SETCURVAR for why we do this.
|
|
curFNDocBlock = NULL;
|
|
curNSDocBlock = NULL;
|
|
break;
|
|
|
|
case OP_LOADVAR_UINT:
|
|
currentRegister = -1;
|
|
stack[_STK + 1].setInt(gEvalState.getIntVariable());
|
|
_STK++;
|
|
break;
|
|
|
|
case OP_LOADVAR_FLT:
|
|
currentRegister = -1;
|
|
stack[_STK + 1].setFloat(gEvalState.getFloatVariable());
|
|
_STK++;
|
|
break;
|
|
|
|
case OP_LOADVAR_STR:
|
|
currentRegister = -1;
|
|
stack[_STK + 1].setString(gEvalState.getStringVariable());
|
|
_STK++;
|
|
break;
|
|
|
|
case OP_SAVEVAR_UINT:
|
|
gEvalState.setIntVariable(stack[_STK].getInt());
|
|
break;
|
|
|
|
case OP_SAVEVAR_FLT:
|
|
gEvalState.setFloatVariable(stack[_STK].getFloat());
|
|
break;
|
|
|
|
case OP_SAVEVAR_STR:
|
|
gEvalState.setStringVariable(stack[_STK].getString());
|
|
break;
|
|
|
|
case OP_LOAD_LOCAL_VAR_UINT:
|
|
reg = code[ip++];
|
|
currentRegister = reg;
|
|
|
|
// See OP_SETCURVAR
|
|
prevField = NULL;
|
|
prevObject = NULL;
|
|
curObject = NULL;
|
|
|
|
stack[_STK + 1].setInt(gEvalState.getLocalIntVariable(reg));
|
|
_STK++;
|
|
break;
|
|
|
|
case OP_LOAD_LOCAL_VAR_FLT:
|
|
reg = code[ip++];
|
|
currentRegister = reg;
|
|
|
|
// See OP_SETCURVAR
|
|
prevField = NULL;
|
|
prevObject = NULL;
|
|
curObject = NULL;
|
|
|
|
stack[_STK + 1].setFloat(gEvalState.getLocalFloatVariable(reg));
|
|
_STK++;
|
|
break;
|
|
|
|
case OP_LOAD_LOCAL_VAR_STR:
|
|
reg = code[ip++];
|
|
currentRegister = reg;
|
|
|
|
// See OP_SETCURVAR
|
|
prevField = NULL;
|
|
prevObject = NULL;
|
|
curObject = NULL;
|
|
|
|
val = gEvalState.getLocalStringVariable(reg);
|
|
stack[_STK + 1].setString(val);
|
|
_STK++;
|
|
break;
|
|
|
|
case OP_SAVE_LOCAL_VAR_UINT:
|
|
reg = code[ip++];
|
|
currentRegister = reg;
|
|
|
|
// See OP_SETCURVAR
|
|
prevField = NULL;
|
|
prevObject = NULL;
|
|
curObject = NULL;
|
|
|
|
gEvalState.setLocalIntVariable(reg, stack[_STK].getInt());
|
|
break;
|
|
|
|
case OP_SAVE_LOCAL_VAR_FLT:
|
|
reg = code[ip++];
|
|
currentRegister = reg;
|
|
|
|
// See OP_SETCURVAR
|
|
prevField = NULL;
|
|
prevObject = NULL;
|
|
curObject = NULL;
|
|
|
|
gEvalState.setLocalFloatVariable(reg, stack[_STK].getFloat());
|
|
break;
|
|
|
|
case OP_SAVE_LOCAL_VAR_STR:
|
|
reg = code[ip++];
|
|
val = stack[_STK].getString();
|
|
currentRegister = reg;
|
|
|
|
// See OP_SETCURVAR
|
|
prevField = NULL;
|
|
prevObject = NULL;
|
|
curObject = NULL;
|
|
|
|
gEvalState.setLocalStringVariable(reg, val, (S32)dStrlen(val));
|
|
break;
|
|
|
|
case OP_SETCUROBJECT:
|
|
// Save the previous object for parsing vector fields.
|
|
prevObject = curObject;
|
|
val = stack[_STK].getString();
|
|
|
|
// 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 wasnt found
|
|
if (curObject)
|
|
{
|
|
SimSet* set = dynamic_cast<SimSet*>(curObject);
|
|
if (set)
|
|
{
|
|
StringTableEntry intName = StringTable->insert(stack[_STK].getString());
|
|
bool recurse = code[ip - 1];
|
|
SimObject* obj = set->findObjectByInternalName(intName, recurse);
|
|
stack[_STK].setInt(obj ? obj->getId() : 0);
|
|
}
|
|
else
|
|
{
|
|
Con::errorf(ConsoleLogEntry::Script, "%s: Attempt to use -> on non-set %s of class %s.", getFileLine(ip - 2), curObject->getName(), curObject->getClassName());
|
|
stack[_STK].setInt(0);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Con::errorf(ConsoleLogEntry::Script, "%s: Attempt to use ->, but the group object wasn't found.", getFileLine(ip - 2));
|
|
stack[_STK].setInt(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, 256);
|
|
curField = CodeToSTE(code, ip);
|
|
curFieldArray[0] = 0;
|
|
ip += 2;
|
|
break;
|
|
|
|
case OP_SETCURFIELD_ARRAY:
|
|
dStrcpy(curFieldArray, stack[_STK].getString(), 256);
|
|
break;
|
|
|
|
case OP_SETCURFIELD_TYPE:
|
|
if(curObject)
|
|
curObject->setDataFieldType(code[ip], curField, curFieldArray);
|
|
ip++;
|
|
break;
|
|
|
|
case OP_LOADFIELD_UINT:
|
|
if (curObject)
|
|
stack[_STK + 1].setInt(dAtol(curObject->getDataField(curField, curFieldArray)));
|
|
else
|
|
{
|
|
// The field is not being retrieved from an object. Maybe it's
|
|
// a special accessor?
|
|
char buff[FieldBufferSizeNumeric];
|
|
memset(buff, 0, sizeof(buff));
|
|
getFieldComponent(prevObject, prevField, prevFieldArray, curField, buff, currentRegister);
|
|
stack[_STK + 1].setInt(dAtol(buff));
|
|
}
|
|
_STK++;
|
|
break;
|
|
|
|
case OP_LOADFIELD_FLT:
|
|
if (curObject)
|
|
stack[_STK + 1].setFloat(dAtod(curObject->getDataField(curField, curFieldArray)));
|
|
else
|
|
{
|
|
// The field is not being retrieved from an object. Maybe it's
|
|
// a special accessor?
|
|
char buff[FieldBufferSizeNumeric];
|
|
memset(buff, 0, sizeof(buff));
|
|
getFieldComponent(prevObject, prevField, prevFieldArray, curField, buff, currentRegister);
|
|
stack[_STK + 1].setFloat(dAtod(buff));
|
|
}
|
|
_STK++;
|
|
break;
|
|
|
|
case OP_LOADFIELD_STR:
|
|
if (curObject)
|
|
{
|
|
val = curObject->getDataField(curField, curFieldArray);
|
|
stack[_STK + 1].setString(val);
|
|
}
|
|
else
|
|
{
|
|
// The field is not being retrieved from an object. Maybe it's
|
|
// a special accessor?
|
|
char buff[FieldBufferSizeString];
|
|
memset(buff, 0, sizeof(buff));
|
|
getFieldComponent(prevObject, prevField, prevFieldArray, curField, buff, currentRegister);
|
|
stack[_STK + 1].setString(buff);
|
|
}
|
|
_STK++;
|
|
break;
|
|
|
|
case OP_SAVEFIELD_UINT:
|
|
if (curObject)
|
|
curObject->setDataField(curField, curFieldArray, stack[_STK].getString());
|
|
else
|
|
{
|
|
// The field is not being set on an object. Maybe it's a special accessor?
|
|
setFieldComponent(prevObject, prevField, prevFieldArray, curField, currentRegister);
|
|
prevObject = NULL;
|
|
}
|
|
break;
|
|
|
|
case OP_SAVEFIELD_FLT:
|
|
if (curObject)
|
|
curObject->setDataField(curField, curFieldArray, stack[_STK].getString());
|
|
else
|
|
{
|
|
// The field is not being set on an object. Maybe it's a special accessor?
|
|
setFieldComponent(prevObject, prevField, prevFieldArray, curField, currentRegister);
|
|
prevObject = NULL;
|
|
}
|
|
break;
|
|
|
|
case OP_SAVEFIELD_STR:
|
|
if (curObject)
|
|
curObject->setDataField(curField, curFieldArray, stack[_STK].getString());
|
|
else
|
|
{
|
|
// The field is not being set on an object. Maybe it's a special accessor?
|
|
setFieldComponent(prevObject, prevField, prevFieldArray, curField, currentRegister);
|
|
prevObject = NULL;
|
|
}
|
|
break;
|
|
|
|
case OP_POP_STK:
|
|
_STK--;
|
|
break;
|
|
|
|
case OP_LOADIMMED_UINT:
|
|
stack[_STK + 1].setInt(code[ip++]);
|
|
_STK++;
|
|
break;
|
|
|
|
case OP_LOADIMMED_FLT:
|
|
stack[_STK + 1].setFloat(curFloatTable[code[ip++]]);
|
|
_STK++;
|
|
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;
|
|
}
|
|
TORQUE_CASE_FALLTHROUGH;
|
|
|
|
case OP_LOADIMMED_STR:
|
|
stack[_STK + 1].setString(curStringTable + code[ip++]);
|
|
_STK ++;
|
|
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:
|
|
stack[_STK + 1].setString(CodeToSTE(code, ip));
|
|
_STK++;
|
|
ip += 2;
|
|
break;
|
|
|
|
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.
|
|
fnName = CodeToSTE(code, ip);
|
|
fnNamespace = CodeToSTE(code, ip + 2);
|
|
U32 callType = code[ip + 4];
|
|
|
|
//if this is called from inside a function, append the ip and codeptr
|
|
if (!gEvalState.stack.empty())
|
|
{
|
|
gEvalState.getCurrentFrame().code = this;
|
|
gEvalState.getCurrentFrame().ip = ip - 1;
|
|
}
|
|
|
|
ip += 5;
|
|
gCallStack.argvc(fnName, callArgc, &callArgv);
|
|
|
|
if (callType == FuncCallExprNode::FunctionCall)
|
|
{
|
|
// Note: This works even if the function was in a package. Reason being is when
|
|
// activatePackage() is called, it swaps the namespaceEntry into the global namespace
|
|
// (and reverts it when deactivatePackage is called). Method or Static related ones work
|
|
// as expected, as the namespace is resolved on the fly.
|
|
nsEntry = Namespace::global()->lookup(fnName);
|
|
if (!nsEntry)
|
|
{
|
|
Con::warnf(ConsoleLogEntry::General,
|
|
"%s: Unable to find function %s",
|
|
getFileLine(ip - 4), fnName);
|
|
|
|
gCallStack.popFrame();
|
|
stack[_STK + 1].setEmptyString();
|
|
_STK++;
|
|
break;
|
|
}
|
|
}
|
|
else if (callType == FuncCallExprNode::StaticCall)
|
|
{
|
|
// Try to look it up.
|
|
ns = Namespace::find(fnNamespace);
|
|
nsEntry = ns->lookup(fnName);
|
|
if (!nsEntry)
|
|
{
|
|
Con::warnf(ConsoleLogEntry::General,
|
|
"%s: Unable to find function %s%s%s",
|
|
getFileLine(ip - 4), fnNamespace ? fnNamespace : "",
|
|
fnNamespace ? "::" : "", fnName);
|
|
|
|
gCallStack.popFrame();
|
|
stack[_STK + 1].setEmptyString();
|
|
_STK++;
|
|
break;
|
|
}
|
|
}
|
|
else if (callType == FuncCallExprNode::MethodCall)
|
|
{
|
|
saveObject = gEvalState.thisObject;
|
|
|
|
// Optimization: If we're an integer, we can lookup the value by SimObjectId
|
|
const ConsoleValue& simObjectLookupValue = callArgv[1];
|
|
if (simObjectLookupValue.getType() == ConsoleValueType::cvInteger)
|
|
gEvalState.thisObject = Sim::findObject(static_cast<SimObjectId>(simObjectLookupValue.getFastInt()));
|
|
else
|
|
{
|
|
SimObject *foundObject = Sim::findObject(simObjectLookupValue.getString());
|
|
|
|
// Optimization: If we're not an integer, let's make it so that the fast path exists
|
|
// on the first argument of the method call (speeds up future usage of %this, for example)
|
|
if (foundObject != NULL)
|
|
callArgv[1].setInt(static_cast<S64>(foundObject->getId()));
|
|
|
|
gEvalState.thisObject = foundObject;
|
|
}
|
|
|
|
if (gEvalState.thisObject == NULL)
|
|
{
|
|
Con::warnf(
|
|
ConsoleLogEntry::General,
|
|
"%s: Unable to find object: '%s' attempting to call function '%s'",
|
|
getFileLine(ip - 6),
|
|
simObjectLookupValue.getString(),
|
|
fnName
|
|
);
|
|
|
|
gCallStack.popFrame();
|
|
stack[_STK + 1].setEmptyString();
|
|
_STK++;
|
|
break;
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
if (!nsEntry || noCalls)
|
|
{
|
|
if (!noCalls)
|
|
{
|
|
Con::warnf(ConsoleLogEntry::General, "%s: Unknown command %s.", getFileLine(ip - 4), 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));
|
|
}
|
|
}
|
|
gCallStack.popFrame();
|
|
stack[_STK + 1].setEmptyString();
|
|
_STK++;
|
|
break;
|
|
}
|
|
if (nsEntry->mType == Namespace::Entry::ConsoleFunctionType)
|
|
{
|
|
if (nsEntry->mFunctionOffset)
|
|
{
|
|
ConsoleValue returnFromFn = nsEntry->mCode->exec(nsEntry->mFunctionOffset, fnName, nsEntry->mNamespace, callArgc, callArgv, false, nsEntry->mPackage);
|
|
stack[_STK + 1] = std::move(returnFromFn);
|
|
}
|
|
else // no body
|
|
stack[_STK + 1].setEmptyString();
|
|
_STK++;
|
|
|
|
gCallStack.popFrame();
|
|
}
|
|
else
|
|
{
|
|
if ((nsEntry->mMinArgs && S32(callArgc) < nsEntry->mMinArgs) || (nsEntry->mMaxArgs && S32(callArgc) > nsEntry->mMaxArgs))
|
|
{
|
|
const char* nsName = ns ? ns->mName : "";
|
|
Con::warnf(ConsoleLogEntry::Script, "%s: %s::%s - wrong number of arguments.", getFileLine(ip - 4), nsName, fnName);
|
|
Con::warnf(ConsoleLogEntry::Script, "%s: usage: %s", getFileLine(ip - 4), nsEntry->mUsage);
|
|
gCallStack.popFrame();
|
|
stack[_STK + 1].setEmptyString();
|
|
_STK++;
|
|
}
|
|
else
|
|
{
|
|
switch (nsEntry->mType)
|
|
{
|
|
case Namespace::Entry::StringCallbackType:
|
|
{
|
|
const char* result = nsEntry->cb.mStringCallbackFunc(gEvalState.thisObject, callArgc, callArgv);
|
|
gCallStack.popFrame();
|
|
stack[_STK + 1].setString(result);
|
|
_STK++;
|
|
break;
|
|
}
|
|
case Namespace::Entry::IntCallbackType:
|
|
{
|
|
S64 result = nsEntry->cb.mIntCallbackFunc(gEvalState.thisObject, callArgc, callArgv);
|
|
gCallStack.popFrame();
|
|
|
|
if (code[ip] == OP_POP_STK)
|
|
{
|
|
ip++;
|
|
break;
|
|
}
|
|
|
|
stack[_STK + 1].setInt(result);
|
|
_STK++;
|
|
break;
|
|
}
|
|
case Namespace::Entry::FloatCallbackType:
|
|
{
|
|
F64 result = nsEntry->cb.mFloatCallbackFunc(gEvalState.thisObject, callArgc, callArgv);
|
|
gCallStack.popFrame();
|
|
|
|
if (code[ip] == OP_POP_STK)
|
|
{
|
|
ip++;
|
|
break;
|
|
}
|
|
|
|
stack[_STK + 1].setFloat(result);
|
|
_STK++;
|
|
break;
|
|
}
|
|
case Namespace::Entry::VoidCallbackType:
|
|
{
|
|
nsEntry->cb.mVoidCallbackFunc(gEvalState.thisObject, callArgc, callArgv);
|
|
gCallStack.popFrame();
|
|
|
|
if (code[ip] == OP_POP_STK)
|
|
{
|
|
ip++;
|
|
break;
|
|
}
|
|
|
|
if (Con::getBoolVariable("$Con::warnVoidAssignment", true))
|
|
{
|
|
Con::warnf(ConsoleLogEntry::General, "%s: Call to %s in %s uses result of void function call.", getFileLine(ip - 4), fnName, functionName);
|
|
}
|
|
|
|
stack[_STK + 1].setEmptyString();
|
|
_STK++;
|
|
|
|
break;
|
|
}
|
|
case Namespace::Entry::BoolCallbackType:
|
|
{
|
|
bool result = nsEntry->cb.mBoolCallbackFunc(gEvalState.thisObject, callArgc, callArgv);
|
|
gCallStack.popFrame();
|
|
|
|
if (code[ip] == OP_POP_STK)
|
|
{
|
|
ip++;
|
|
break;
|
|
}
|
|
|
|
stack[_STK + 1].setBool(result);
|
|
_STK++;
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (callType == FuncCallExprNode::MethodCall)
|
|
gEvalState.thisObject = saveObject;
|
|
break;
|
|
}
|
|
|
|
case OP_ADVANCE_STR_APPENDCHAR:
|
|
{
|
|
char buff[2];
|
|
buff[0] = (char)code[ip++];
|
|
buff[1] = '\0';
|
|
|
|
S32 len;
|
|
const char* concat = tsconcat(stack[_STK].getString(), buff, len);
|
|
|
|
stack[_STK].setStringRef(concat, len);
|
|
break;
|
|
}
|
|
|
|
case OP_REWIND_STR:
|
|
TORQUE_CASE_FALLTHROUGH;
|
|
case OP_TERMINATE_REWIND_STR:
|
|
{
|
|
S32 len;
|
|
const char* concat = tsconcat(stack[_STK - 1].getString(), stack[_STK].getString(), len);
|
|
|
|
stack[_STK - 1].setStringRef(concat, len);
|
|
_STK--;
|
|
break;
|
|
}
|
|
|
|
case OP_COMPARE_STR:
|
|
stack[_STK - 1].setBool(!dStricmp(stack[_STK].getString(), stack[_STK - 1].getString()));
|
|
_STK--;
|
|
break;
|
|
|
|
case OP_PUSH:
|
|
gCallStack.push(std::move(stack[_STK--]));
|
|
break;
|
|
|
|
case OP_PUSH_FRAME:
|
|
gCallStack.pushFrame(code[ip++]);
|
|
break;
|
|
|
|
case OP_ASSERT:
|
|
{
|
|
if (!stack[_STK--].getBool())
|
|
{
|
|
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.stack.empty(), "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;
|
|
TORQUE_CASE_FALLTHROUGH;
|
|
}
|
|
|
|
case OP_ITER_BEGIN:
|
|
{
|
|
bool isGlobal = code[ip];
|
|
|
|
U32 failIp = code[ip + (isGlobal ? 3 : 2)];
|
|
|
|
IterStackRecord& iter = iterStack[_ITER];
|
|
iter.mIsGlobalVariable = isGlobal;
|
|
|
|
if (isGlobal)
|
|
{
|
|
StringTableEntry varName = CodeToSTE(code, ip + 1);
|
|
iter.mVar.mVariable = gEvalState.globalVars.add(varName);
|
|
}
|
|
else
|
|
{
|
|
iter.mVar.mRegister = code[ip + 1];
|
|
}
|
|
|
|
if (iter.mIsStringIter)
|
|
{
|
|
iter.mData.mStr.mString = stack[_STK].getString();
|
|
iter.mData.mStr.mIndex = 0;
|
|
}
|
|
else
|
|
{
|
|
// Look up the object.
|
|
|
|
SimSet* set;
|
|
if (!Sim::findObject(stack[_STK].getString(), set))
|
|
{
|
|
Con::errorf(ConsoleLogEntry::General, "No SimSet object '%s'", stack[_STK].getString());
|
|
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++;
|
|
|
|
ip += isGlobal ? 4 : 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.
|
|
|
|
if (iter.mIsGlobalVariable)
|
|
iter.mVar.mVariable->setStringValue(&str[startIndex]);
|
|
else
|
|
gEvalState.setLocalStringVariable(iter.mVar.mRegister, &str[startIndex], endIndex - startIndex);
|
|
|
|
const_cast<char*>(str)[endIndex] = savedChar;
|
|
}
|
|
else
|
|
{
|
|
if (iter.mIsGlobalVariable)
|
|
iter.mVar.mVariable->setStringValue("");
|
|
else
|
|
gEvalState.setLocalStringVariable(iter.mVar.mRegister, "", 0);
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
SimObjectId id = set->at(index)->getId();
|
|
|
|
if (iter.mIsGlobalVariable)
|
|
iter.mVar.mVariable->setIntValue(id);
|
|
else
|
|
gEvalState.setLocalIntVariable(iter.mVar.mRegister, id);
|
|
|
|
iter.mData.mObj.mIndex = index + 1;
|
|
}
|
|
|
|
++ip;
|
|
break;
|
|
}
|
|
|
|
case OP_ITER_END:
|
|
{
|
|
--_ITER;
|
|
--iterDepth;
|
|
|
|
_STK--;
|
|
|
|
iterStack[_ITER].mIsStringIter = false;
|
|
break;
|
|
}
|
|
|
|
case OP_INVALID:
|
|
TORQUE_CASE_FALLTHROUGH;
|
|
default:
|
|
// error!
|
|
AssertISV(false, "Invalid OPCode Processed!");
|
|
goto execFinished;
|
|
}
|
|
}
|
|
execFinished:
|
|
|
|
if (telDebuggerOn && setFrame < 0)
|
|
TelDebugger->popStackFrame();
|
|
|
|
if (popFrame)
|
|
{
|
|
gEvalState.popFrame();
|
|
}
|
|
|
|
if (argv)
|
|
{
|
|
if (gEvalState.traceOn)
|
|
{
|
|
traceBuffer[0] = 0;
|
|
dStrcat(traceBuffer, "Leaving ", TRACE_BUFFER_SIZE);
|
|
|
|
if (packageName)
|
|
{
|
|
dStrcat(traceBuffer, "[", TRACE_BUFFER_SIZE);
|
|
dStrcat(traceBuffer, packageName, TRACE_BUFFER_SIZE);
|
|
dStrcat(traceBuffer, "]", TRACE_BUFFER_SIZE);
|
|
}
|
|
if (thisNamespace && thisNamespace->mName)
|
|
{
|
|
dSprintf(traceBuffer + (U32)dStrlen(traceBuffer), sizeof(traceBuffer) - (U32)dStrlen(traceBuffer),
|
|
"%s::%s() - return %s", thisNamespace->mName, thisFunctionName, returnValue.getString());
|
|
}
|
|
else
|
|
{
|
|
dSprintf(traceBuffer + (U32)dStrlen(traceBuffer), sizeof(traceBuffer) - (U32)dStrlen(traceBuffer),
|
|
"%s() - return %s", thisFunctionName, returnValue.getString());
|
|
}
|
|
Con::printf("%s", traceBuffer);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
delete[] const_cast<char*>(globalStrings);
|
|
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(!(_STK > stackStart), "String stack not popped enough in script exec");
|
|
AssertFatal(!(_STK < stackStart), "String stack popped too much in script exec");
|
|
#endif
|
|
|
|
return returnValue;
|
|
}
|
|
|
|
//------------------------------------------------------------
|