diff --git a/Engine/source/console/astNodes.cpp b/Engine/source/console/astNodes.cpp index 28e6b4960..14f610bd2 100644 --- a/Engine/source/console/astNodes.cpp +++ b/Engine/source/console/astNodes.cpp @@ -413,11 +413,18 @@ U32 IterStmtNode::compileStmt(CodeStream& codeStream, U32 ip) codeStream.pushFixScope(true); + bool isGlobal = varName[0] == '$'; + TypeReq varType = isStringIter ? TypeReqString : TypeReqUInt; + const U32 startIp = ip; - containerExpr->compile(codeStream, startIp, TypeReqString); + containerExpr->compile(codeStream, startIp, TypeReqString); // todo: figure out better way to codegen this so we don't rely on STR codeStream.emit(isStringIter ? OP_ITER_BEGIN_STR : OP_ITER_BEGIN); - codeStream.emitSTE(varName); + codeStream.emit(isGlobal); + if (isGlobal) + codeStream.emitSTE(varName); + else + codeStream.emit(gFuncVars->assign(varName, varType)); const U32 finalFix = codeStream.emit(0); const U32 continueIp = codeStream.emit(OP_ITER); codeStream.emitFix(CodeStream::FIXTYPE_BREAK); diff --git a/Engine/source/console/codeBlock.cpp b/Engine/source/console/codeBlock.cpp index d3687a2cd..559e921c3 100644 --- a/Engine/source/console/codeBlock.cpp +++ b/Engine/source/console/codeBlock.cpp @@ -643,7 +643,8 @@ ConsoleValue CodeBlock::compileExec(StringTableEntry fileName, const char *inStr codeStream.emit(OP_RETURN); codeStream.emitCodeStream(&codeSize, &code, &lineBreakPairs); - //dumpInstructions(0, false); + if (Con::getBoolVariable("dump")) + dumpInstructions(0, false); consoleAllocReset(); @@ -1392,23 +1393,50 @@ void CodeBlock::dumpInstructions(U32 startIp, bool upToReturn) case OP_ITER_BEGIN: { - StringTableEntry varName = CodeToSTE(code, ip); - U32 failIp = code[ip + 2]; + bool isGlobal = code[ip]; + if (isGlobal) + { + StringTableEntry varName = CodeToSTE(code, ip + 1); + U32 failIp = code[ip + 3]; - Con::printf("%i: OP_ITER_BEGIN varName=%s failIp=%i", ip - 1, varName, failIp); + Con::printf("%i: OP_ITER_BEGIN varName=%s failIp=%i isGlobal=%s", ip - 1, varName, failIp, "true"); - ip += 3; + ip += 4; + } + else + { + S32 reg = code[ip + 1]; + U32 failIp = code[ip + 2]; + + Con::printf("%i: OP_ITER_BEGIN varRegister=%d failIp=%i isGlobal=%s", ip - 1, reg, failIp, "false"); + + ip += 3; + } break; } case OP_ITER_BEGIN_STR: { - StringTableEntry varName = CodeToSTE(code, ip); - U32 failIp = code[ip + 2]; + bool isGlobal = code[ip]; + if (isGlobal) + { + StringTableEntry varName = CodeToSTE(code, ip + 1); + U32 failIp = code[ip + 3]; - Con::printf("%i: OP_ITER_BEGIN varName=%s failIp=%i", ip - 1, varName, failIp); + Con::printf("%i: OP_ITER_BEGIN_STR varName=%s failIp=%i isGlobal=%s", ip - 1, varName, failIp, "true"); + + ip += 4; + } + else + { + S32 reg = code[ip + 1]; + U32 failIp = code[ip + 2]; + + Con::printf("%i: OP_ITER_BEGIN_STR varRegister=%d failIp=%i isGlobal=%s", ip - 1, reg, failIp, "false"); + + ip += 3; + } - ip += 3; break; } diff --git a/Engine/source/console/compiledEval.cpp b/Engine/source/console/compiledEval.cpp index 0375ab14e..23245b135 100644 --- a/Engine/source/console/compiledEval.cpp +++ b/Engine/source/console/compiledEval.cpp @@ -66,8 +66,18 @@ struct IterStackRecord /// If true, this is a foreach$ loop; if not, it's a foreach loop. bool mIsStringIter; - /// The iterator variable. - Dictionary::Entry* mVariable; + /// 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 @@ -1745,6 +1755,7 @@ ConsoleValue CodeBlock::exec(U32 ip, const char* functionName, Namespace* thisNa { if (nsEntry->mFunctionOffset) { + // TODO: not make this strings only for returns. ConsoleValue returnFromFn = nsEntry->mCode->exec(nsEntry->mFunctionOffset, fnName, nsEntry->mNamespace, callArgc, callArgv, false, nsEntry->mPackage); STR.setStringValue(returnFromFn.getString()); } @@ -1954,12 +1965,22 @@ ConsoleValue CodeBlock::exec(U32 ip, const char* functionName, Namespace* thisNa case OP_ITER_BEGIN: { - StringTableEntry varName = CodeToSTE(code, ip); - U32 failIp = code[ip + 2]; + bool isGlobal = code[ip]; + + U32 failIp = code[ip + isGlobal ? 3 : 2]; IterStackRecord& iter = iterStack[_ITER]; + iter.mIsGlobalVariable = isGlobal; - iter.mVariable = gEvalState.getCurrentFrame().add(varName); + 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) { @@ -1990,7 +2011,7 @@ ConsoleValue CodeBlock::exec(U32 ip, const char* functionName, Namespace* thisNa STR.push(); - ip += 3; + ip += isGlobal ? 4 : 3; break; } @@ -2026,11 +2047,21 @@ ConsoleValue CodeBlock::exec(U32 ip, const char* functionName, Namespace* thisNa { char savedChar = str[endIndex]; const_cast(str)[endIndex] = '\0'; // We are on the string stack so this is okay. - iter.mVariable->setStringValue(&str[startIndex]); + + 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 - iter.mVariable->setStringValue(""); + { + if (iter.mIsGlobalVariable) + iter.mVar.mVariable->setStringValue(""); + else + gEvalState.setLocalStringVariable(iter.mVar.mRegister, "", 0); + } // Skip separator. if (str[endIndex] != '\0') @@ -2049,7 +2080,13 @@ ConsoleValue CodeBlock::exec(U32 ip, const char* functionName, Namespace* thisNa continue; } - iter.mVariable->setIntValue(set->at(index)->getId()); + 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; } diff --git a/Engine/source/console/test/ScriptTest.cpp b/Engine/source/console/test/ScriptTest.cpp index 3bd6a1bb4..ecf3ae3f9 100644 --- a/Engine/source/console/test/ScriptTest.cpp +++ b/Engine/source/console/test/ScriptTest.cpp @@ -241,9 +241,11 @@ TEST(Script, Basic_Loop_Statements) ASSERT_STREQ(forValue.getString(), "aaa"); ConsoleValue forIfValue = RunScript(R"( - function t() { + function t() + { %str = ""; - for (%i = 0; %i < 5; %i++) { + for (%i = 0; %i < 5; %i++) + { %loopValue = %i; @@ -261,6 +263,82 @@ TEST(Script, Basic_Loop_Statements) ASSERT_STREQ(forIfValue.getString(), "0, 1, 2, 3, 4"); } +TEST(Script, ForEachLoop) +{ + ConsoleValue forEach1 = RunScript(R"( + $theSimSet = new SimSet(); + $theSimSet.add(new SimObject()); + $theSimSet.add(new SimObject()); + + $counter = 0; + foreach ($obj in $theSimSet) + { + $counter++; + } + + $theSimSet.delete(); + + return $counter; + )"); + + ASSERT_EQ(forEach1.getInt(), 2); + + ConsoleValue forEach2 = RunScript(R"( + $counter = 0; + foreach$ ($word in "a b c d") + { + $counter++; + } + + return $counter; + )"); + + ASSERT_EQ(forEach2.getInt(), 4); + + ConsoleValue forEach3 = RunScript(R"( + function SimObject::addOne(%this) + { + return 1; + } + + function test() + { + %set = new SimSet(); + %set.add(new SimObject()); + %set.add(new SimObject()); + + %count = 0; + foreach (%obj in %set) + %count += %obj.addOne(); + + %set.delete(); + + return %count; + } + + return test(); + )"); + + ASSERT_EQ(forEach3.getInt(), 2); + + ConsoleValue forEach4 = RunScript(R"( + function test() + { + %string = "a b c d e"; + + %count = 0; + foreach$ (%word in %string) + %count++; + + return %count; + } + + return test(); + )"); + + ASSERT_EQ(forEach4.getInt(), 5); +} + TEST(Script, TorqueScript_Array_Testing) { ConsoleValue value = RunScript(R"( @@ -281,7 +359,8 @@ TEST(Script, TorqueScript_Array_Testing) TEST(Script, Basic_SimObject) { ConsoleValue object = RunScript(R"( - return new SimObject(FudgeCollector) { + return new SimObject(FudgeCollector) + { fudge = "Chocolate"; }; )"); @@ -329,7 +408,8 @@ TEST(Script, Basic_Package) { ConsoleValue value = RunScript(R"( function a() { return 3; } - package overrides { + package overrides + { function a() { return 5; } }; return a();