//----------------------------------------------------------------------------- // 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; char curFieldArray[256]; char prevFieldArray[256]; 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 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 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(); } } //----------------------------------------------------------------------------- enum class IntegerOperation { BitAnd, BitOr, Xor, LShift, RShift, LogicalAnd, LogicalOr }; template 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].setInt(a.getInt() && b.getInt()); if constexpr (Op == IntegerOperation::LogicalOr) stack[_STK - 1].setInt(a.getInt() || b.getInt()); _STK--; } template 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].setFastInt(a.getFastInt() && b.getFastInt()); if constexpr (Op == IntegerOperation::LogicalOr) stack[_STK - 1].setFastInt(a.getFastInt() || b.getFastInt()); _STK--; } else { doSlowIntegerOp(); } } //----------------------------------------------------------------------------- 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 dsize_t 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 + 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 < 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 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(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(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(parent); if (parent_db) { SimDataBlock* currentNewObject_db = dynamic_cast(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(parent); if (parent_db) { SimDataBlock* currentNewObject_db = dynamic_cast(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(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(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(); break; case OP_CMPGR: doFloatMathOperation(); break; case OP_CMPGE: doFloatMathOperation(); break; case OP_CMPLT: doFloatMathOperation(); break; case OP_CMPLE: doFloatMathOperation(); break; case OP_CMPNE: doFloatMathOperation(); break; case OP_XOR: doIntOperation(); break; case OP_BITAND: doIntOperation(); break; case OP_BITOR: doIntOperation(); break; case OP_NOT: stack[_STK].setInt(!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(); break; case OP_SHL: doIntOperation(); break; case OP_AND: doIntOperation(); break; case OP_OR: doIntOperation(); break; case OP_ADD: doFloatMathOperation(); break; case OP_SUB: doFloatMathOperation(); break; case OP_MUL: doFloatMathOperation(); break; case OP_DIV: doFloatMathOperation(); 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(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(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(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(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(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 + dStrlen(traceBuffer), sizeof(traceBuffer) - dStrlen(traceBuffer), "%s::%s() - return %s", thisNamespace->mName, thisFunctionName, returnValue.getString()); } else { dSprintf(traceBuffer + dStrlen(traceBuffer), sizeof(traceBuffer) - dStrlen(traceBuffer), "%s() - return %s", thisFunctionName, returnValue.getString()); } Con::printf("%s", traceBuffer); } } else { delete[] const_cast(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 std::move(returnValue); } //------------------------------------------------------------