forked from Mirrors/openclonk
1095 lines
27 KiB
C++
1095 lines
27 KiB
C++
/*
|
|
* OpenClonk, http://www.openclonk.org
|
|
*
|
|
* Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
|
|
* Copyright (c) 2009-2016, The OpenClonk Team and contributors
|
|
*
|
|
* Distributed under the terms of the ISC license; see accompanying file
|
|
* "COPYING" for details.
|
|
*
|
|
* "Clonk" is a registered trademark of Matthes Bender, used with permission.
|
|
* See accompanying file "TRADEMARK" for details.
|
|
*
|
|
* To redistribute this file separately, substitute the full license texts
|
|
* for the above references.
|
|
*/
|
|
// executes script functions
|
|
|
|
#include "C4Include.h"
|
|
#include "script/C4AulExec.h"
|
|
|
|
#include "control/C4Record.h"
|
|
#include "object/C4Def.h"
|
|
#include "object/C4Object.h"
|
|
#include "script/C4Aul.h"
|
|
#include "script/C4AulScriptFunc.h"
|
|
#include "script/C4AulDebug.h"
|
|
#include "script/C4ScriptHost.h"
|
|
|
|
C4AulExec AulExec;
|
|
|
|
C4AulExecError::C4AulExecError(const char *szError)
|
|
{
|
|
assert(szError);
|
|
// direct error message string
|
|
sMessage.Copy(szError ? szError : "(no error message)");
|
|
}
|
|
|
|
StdStrBuf C4AulScriptContext::ReturnDump(StdStrBuf Dump)
|
|
{
|
|
if (!Func)
|
|
return StdStrBuf("");
|
|
// Context
|
|
if (Obj && Obj->Status)
|
|
{
|
|
C4Value ObjVal(Obj);
|
|
Dump.Append(ObjVal.GetDataString(0));
|
|
Dump.Append("->");
|
|
}
|
|
bool fDirectExec = !Func->GetName();
|
|
if (!fDirectExec)
|
|
{
|
|
// Function name
|
|
Dump.Append(Func->GetName());
|
|
// Parameters
|
|
Dump.AppendChar('(');
|
|
int iNullPars = 0;
|
|
for (int i = 0; i < Func->GetParCount(); i++)
|
|
{
|
|
if (!Pars[i])
|
|
iNullPars++;
|
|
else
|
|
{
|
|
if (i > iNullPars)
|
|
Dump.AppendChar(',');
|
|
// Insert missing null parameters
|
|
while (iNullPars > 0)
|
|
{
|
|
Dump.Append("0,");
|
|
iNullPars--;
|
|
}
|
|
// Insert parameter
|
|
Dump.Append(Pars[i].GetDataString());
|
|
}
|
|
}
|
|
Dump.AppendChar(')');
|
|
}
|
|
else
|
|
Dump.Append(Func->Parent->GetDataString());
|
|
// Script
|
|
if (!fDirectExec && Func->pOrgScript)
|
|
Dump.AppendFormat(" (%s:%d)",
|
|
Func->pOrgScript->ScriptName.getData(),
|
|
CPos ? Func->GetLineOfCode(CPos) : SGetLine(Func->pOrgScript->GetScript(), Func->Script));
|
|
// Return it
|
|
return Dump;
|
|
}
|
|
|
|
void C4AulScriptContext::dump(StdStrBuf Dump)
|
|
{
|
|
// Log it
|
|
DebugLog(ReturnDump(Dump).getData());
|
|
}
|
|
|
|
void C4AulExec::LogCallStack()
|
|
{
|
|
for (C4AulScriptContext *pCtx = pCurCtx; pCtx >= Contexts; pCtx--)
|
|
pCtx->dump(StdStrBuf(" by: "));
|
|
}
|
|
|
|
C4String *C4AulExec::FnTranslate(C4PropList * _this, C4String *text)
|
|
{
|
|
#define ReturnIfTranslationAvailable(script, key) do \
|
|
{ \
|
|
const auto &s = script; \
|
|
const auto &k = key; \
|
|
if (s) \
|
|
{ \
|
|
try \
|
|
{ \
|
|
return ::Strings.RegString(s->Translate(k).c_str()); \
|
|
} \
|
|
catch (C4LangStringTable::NoSuchTranslation &) {} \
|
|
} \
|
|
} while(0)
|
|
|
|
if (!text || text->GetData().isNull()) return nullptr;
|
|
// Find correct script: translations of the context if possible, containing script as fallback
|
|
if (_this && _this->GetDef())
|
|
ReturnIfTranslationAvailable(&(_this->GetDef()->Script), text->GetCStr());
|
|
ReturnIfTranslationAvailable(AulExec.pCurCtx[0].Func->pOrgScript, text->GetCStr());
|
|
|
|
// No translation available, log
|
|
DebugLogF(R"(WARNING: Translate: no translation for string "%s")", text->GetCStr());
|
|
// Trace
|
|
AulExec.LogCallStack();
|
|
return text;
|
|
#undef ReturnIfTranslationAvailable
|
|
}
|
|
|
|
bool C4AulExec::FnLogCallStack(C4PropList * _this)
|
|
{
|
|
AulExec.LogCallStack();
|
|
return true;
|
|
}
|
|
|
|
void C4AulExec::ClearPointers(C4Object * obj)
|
|
{
|
|
for (C4AulScriptContext *pCtx = pCurCtx; pCtx >= Contexts; pCtx--)
|
|
{
|
|
if (pCtx->Obj == obj)
|
|
pCtx->Obj = nullptr;
|
|
}
|
|
}
|
|
|
|
C4Value C4AulExec::Exec(C4AulScriptFunc *pSFunc, C4PropList * p, C4Value *pnPars, bool fPassErrors)
|
|
{
|
|
// Save start context
|
|
C4AulScriptContext *pOldCtx = pCurCtx;
|
|
C4Value *pPars = pCurVal + 1;
|
|
try
|
|
{
|
|
// Push parameters
|
|
assert(pnPars);
|
|
for (int i = 0; i < pSFunc->GetParCount(); i++)
|
|
PushValue(pnPars[i]);
|
|
|
|
// Push a new context
|
|
C4AulScriptContext ctx;
|
|
ctx.tTime = 0;
|
|
ctx.Obj = p;
|
|
ctx.Return = nullptr;
|
|
ctx.Pars = pPars;
|
|
ctx.Func = pSFunc;
|
|
ctx.CPos = nullptr;
|
|
PushContext(ctx);
|
|
|
|
// Execute
|
|
return Exec(pSFunc->GetCode());
|
|
}
|
|
catch (C4AulError &e)
|
|
{
|
|
if (!fPassErrors)
|
|
::ScriptEngine.GetErrorHandler()->OnError(e.what());
|
|
// Unwind stack
|
|
// TODO: The stack dump should be passed to the error handler somehow
|
|
while (pCurCtx > pOldCtx)
|
|
{
|
|
pCurCtx->dump(StdStrBuf(" by: "));
|
|
PopContext();
|
|
}
|
|
PopValuesUntil(pPars - 1);
|
|
// Pass?
|
|
if (fPassErrors)
|
|
throw;
|
|
// Trace
|
|
LogCallStack();
|
|
}
|
|
|
|
// Return nothing
|
|
return C4VNull;
|
|
}
|
|
|
|
C4Value C4AulExec::Exec(C4AulBCC *pCPos)
|
|
{
|
|
try
|
|
{
|
|
|
|
for (;;)
|
|
{
|
|
|
|
bool fJump = false;
|
|
switch (pCPos->bccType)
|
|
{
|
|
case AB_INT:
|
|
PushInt(pCPos->Par.i);
|
|
break;
|
|
|
|
case AB_BOOL:
|
|
PushBool(!!pCPos->Par.i);
|
|
break;
|
|
|
|
case AB_STRING:
|
|
PushString(pCPos->Par.s);
|
|
break;
|
|
|
|
case AB_CPROPLIST:
|
|
PushPropList(pCPos->Par.p);
|
|
break;
|
|
|
|
case AB_CARRAY:
|
|
PushArray(pCPos->Par.a);
|
|
break;
|
|
|
|
case AB_CFUNCTION:
|
|
PushFunction(pCPos->Par.f);
|
|
break;
|
|
|
|
case AB_NIL:
|
|
PushValue(C4VNull);
|
|
break;
|
|
|
|
case AB_DUP:
|
|
PushValue(pCurVal[pCPos->Par.i]);
|
|
break;
|
|
case AB_STACK_SET:
|
|
pCurVal[pCPos->Par.i] = pCurVal[0];
|
|
break;
|
|
case AB_POP_TO:
|
|
pCurVal[pCPos->Par.i] = pCurVal[0];
|
|
PopValue();
|
|
break;
|
|
|
|
case AB_EOFN:
|
|
throw C4AulExecError("internal error: function didn't return");
|
|
|
|
case AB_ERR:
|
|
if (pCPos->Par.s)
|
|
throw C4AulExecError((std::string("syntax error: ") + pCPos->Par.s->GetCStr()).c_str());
|
|
else
|
|
throw C4AulExecError("syntax error: see above for details");
|
|
|
|
case AB_DUP_CONTEXT:
|
|
PushValue(AulExec.GetContext(AulExec.GetContextDepth()-2)->Pars[pCPos->Par.i]);
|
|
break;
|
|
|
|
case AB_LOCALN:
|
|
if (!pCurCtx->Obj)
|
|
throw C4AulExecError("can't access local variables without this");
|
|
PushNullVals(1);
|
|
pCurCtx->Obj->GetPropertyByS(pCPos->Par.s, pCurVal);
|
|
break;
|
|
case AB_LOCALN_SET:
|
|
if (!pCurCtx->Obj)
|
|
throw C4AulExecError("can't access local variables without this");
|
|
if (pCurCtx->Obj->IsFrozen())
|
|
throw C4AulExecError("local variable: this is readonly");
|
|
pCurCtx->Obj->SetPropertyByS(pCPos->Par.s, pCurVal[0]);
|
|
break;
|
|
|
|
case AB_PROP:
|
|
if (!pCurVal->CheckConversion(C4V_PropList))
|
|
throw C4AulExecError(FormatString("proplist access: proplist expected, got %s", pCurVal->GetTypeName()).getData());
|
|
if (!pCurVal->_getPropList()->GetPropertyByS(pCPos->Par.s, pCurVal))
|
|
pCurVal->Set0();
|
|
break;
|
|
case AB_PROP_SET:
|
|
{
|
|
C4Value *pPropList = pCurVal - 1;
|
|
if (!pPropList->CheckConversion(C4V_PropList))
|
|
throw C4AulExecError(FormatString("proplist write: proplist expected, got %s", pPropList->GetTypeName()).getData());
|
|
if (pPropList->_getPropList()->IsFrozen())
|
|
throw C4AulExecError("proplist write: proplist is readonly");
|
|
pPropList->_getPropList()->SetPropertyByS(pCPos->Par.s, pCurVal[0]);
|
|
pPropList->Set(pCurVal[0]);
|
|
PopValue();
|
|
break;
|
|
}
|
|
|
|
case AB_GLOBALN:
|
|
PushValue(*::ScriptEngine.GlobalNamed.GetItem(pCPos->Par.i));
|
|
break;
|
|
case AB_GLOBALN_SET:
|
|
::ScriptEngine.GlobalNamed.GetItem(pCPos->Par.i)->Set(pCurVal[0]);
|
|
break;
|
|
|
|
// prefix
|
|
case AB_BitNot: // ~
|
|
CheckOpPar(C4V_Int, "~");
|
|
pCurVal->SetInt(~pCurVal->_getInt());
|
|
break;
|
|
case AB_Not: // !
|
|
pCurVal->SetBool(!pCurVal->getBool());
|
|
break;
|
|
case AB_Neg: // -
|
|
CheckOpPar(C4V_Int, "-");
|
|
pCurVal->SetInt(-pCurVal->_getInt());
|
|
break;
|
|
case AB_Inc: // ++
|
|
CheckOpPar(C4V_Int, "++");
|
|
pCurVal->SetInt(pCurVal->_getInt() + 1);
|
|
break;
|
|
case AB_Dec: // --
|
|
CheckOpPar(C4V_Int, "--");
|
|
pCurVal->SetInt(pCurVal->_getInt() - 1);
|
|
break;
|
|
// postfix
|
|
case AB_Pow: // **
|
|
{
|
|
CheckOpPars(C4V_Int, C4V_Int, "**");
|
|
C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
|
|
pPar1->SetInt(Pow(pPar1->_getInt(), pPar2->_getInt()));
|
|
PopValue();
|
|
break;
|
|
}
|
|
case AB_Div: // /
|
|
{
|
|
CheckOpPars(C4V_Int, C4V_Int, "/");
|
|
C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
|
|
if (!pPar2->_getInt())
|
|
throw C4AulExecError("division by zero");
|
|
// INT_MIN/-1 cannot be represented in an int and would cause an uncaught exception
|
|
if (pPar1->_getInt()==INT32_MIN && pPar2->_getInt()==-1)
|
|
throw C4AulExecError("division overflow");
|
|
pPar1->SetInt(pPar1->_getInt() / pPar2->_getInt());
|
|
PopValue();
|
|
break;
|
|
}
|
|
case AB_Mul: // *
|
|
{
|
|
CheckOpPars(C4V_Int, C4V_Int, "*");
|
|
C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
|
|
pPar1->SetInt(pPar1->_getInt() * pPar2->_getInt());
|
|
PopValue();
|
|
break;
|
|
}
|
|
case AB_Mod: // %
|
|
{
|
|
CheckOpPars(C4V_Int, C4V_Int, "%");
|
|
C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
|
|
// INT_MIN%-1 cannot be represented in an int and would cause an uncaught exception
|
|
if (pPar1->_getInt()==INT32_MIN && pPar2->_getInt()==-1)
|
|
throw C4AulExecError("modulo division overflow");
|
|
if (pPar2->_getInt())
|
|
pPar1->SetInt(pPar1->_getInt() % pPar2->_getInt());
|
|
else
|
|
pPar1->Set0();
|
|
PopValue();
|
|
break;
|
|
}
|
|
case AB_Sub: // -
|
|
{
|
|
CheckOpPars(C4V_Int, C4V_Int, "-");
|
|
C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
|
|
pPar1->SetInt(pPar1->_getInt() - pPar2->_getInt());
|
|
PopValue();
|
|
break;
|
|
}
|
|
case AB_Sum: // +
|
|
{
|
|
CheckOpPars(C4V_Int, C4V_Int, "+");
|
|
C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
|
|
pPar1->SetInt(pPar1->_getInt() + pPar2->_getInt());
|
|
PopValue();
|
|
break;
|
|
}
|
|
case AB_LeftShift: // <<
|
|
{
|
|
CheckOpPars(C4V_Int, C4V_Int, "<<");
|
|
C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
|
|
pPar1->SetInt(pPar1->_getInt() << pPar2->_getInt());
|
|
PopValue();
|
|
break;
|
|
}
|
|
case AB_RightShift: // >>
|
|
{
|
|
CheckOpPars(C4V_Int, C4V_Int, ">>");
|
|
C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
|
|
pPar1->SetInt(pPar1->_getInt() >> pPar2->_getInt());
|
|
PopValue();
|
|
break;
|
|
}
|
|
case AB_LessThan: // <
|
|
{
|
|
CheckOpPars(C4V_Int, C4V_Int, "<");
|
|
C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
|
|
pPar1->SetBool(pPar1->_getInt() < pPar2->_getInt());
|
|
PopValue();
|
|
break;
|
|
}
|
|
case AB_LessThanEqual: // <=
|
|
{
|
|
CheckOpPars(C4V_Int, C4V_Int, "<=");
|
|
C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
|
|
pPar1->SetBool(pPar1->_getInt() <= pPar2->_getInt());
|
|
PopValue();
|
|
break;
|
|
}
|
|
case AB_GreaterThan: // >
|
|
{
|
|
CheckOpPars(C4V_Int, C4V_Int, ">");
|
|
C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
|
|
pPar1->SetBool(pPar1->_getInt() > pPar2->_getInt());
|
|
PopValue();
|
|
break;
|
|
}
|
|
case AB_GreaterThanEqual: // >=
|
|
{
|
|
CheckOpPars(C4V_Int, C4V_Int, ">=");
|
|
C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
|
|
pPar1->SetBool(pPar1->_getInt() >= pPar2->_getInt());
|
|
PopValue();
|
|
break;
|
|
}
|
|
case AB_Equal: // ==
|
|
{
|
|
C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
|
|
pPar1->SetBool(pPar1->IsIdenticalTo(*pPar2));
|
|
PopValue();
|
|
break;
|
|
}
|
|
case AB_NotEqual: // !=
|
|
{
|
|
C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
|
|
pPar1->SetBool(!pPar1->IsIdenticalTo(*pPar2));
|
|
PopValue();
|
|
break;
|
|
}
|
|
case AB_BitAnd: // &
|
|
{
|
|
CheckOpPars(C4V_Int, C4V_Int, "&");
|
|
C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
|
|
pPar1->SetInt(pPar1->_getInt() & pPar2->_getInt());
|
|
PopValue();
|
|
break;
|
|
}
|
|
case AB_BitXOr: // ^
|
|
{
|
|
CheckOpPars(C4V_Int, C4V_Int, "^");
|
|
C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
|
|
pPar1->SetInt(pPar1->_getInt() ^ pPar2->_getInt());
|
|
PopValue();
|
|
break;
|
|
}
|
|
case AB_BitOr: // |
|
|
{
|
|
CheckOpPars(C4V_Int, C4V_Int, "|");
|
|
C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
|
|
pPar1->SetInt(pPar1->_getInt() | pPar2->_getInt());
|
|
PopValue();
|
|
break;
|
|
}
|
|
|
|
case AB_NEW_ARRAY:
|
|
{
|
|
// Create array
|
|
C4ValueArray *pArray = new C4ValueArray(pCPos->Par.i);
|
|
|
|
// Pop values from stack
|
|
for (int i = 0; i < pCPos->Par.i; i++)
|
|
(*pArray)[i] = pCurVal[i - pCPos->Par.i + 1];
|
|
|
|
// Push array
|
|
PopValues(pCPos->Par.i);
|
|
PushArray(pArray);
|
|
|
|
break;
|
|
}
|
|
|
|
case AB_NEW_PROPLIST:
|
|
{
|
|
C4PropList * pPropList = C4PropList::New();
|
|
|
|
for (int i = 0; i < pCPos->Par.i; i++)
|
|
pPropList->SetPropertyByS(pCurVal[-2 * i - 1]._getStr(), pCurVal[-2 * i]);
|
|
|
|
PopValues(pCPos->Par.i * 2);
|
|
PushPropList(pPropList);
|
|
break;
|
|
}
|
|
|
|
case AB_ARRAYA:
|
|
{
|
|
C4Value *pIndex = pCurVal, *pStruct = pCurVal - 1, *pResult = pCurVal - 1;
|
|
// Typcheck to determine whether it's an array or a proplist
|
|
if(CheckArrayAccess(pStruct, pIndex) == C4V_Array)
|
|
{
|
|
*pResult = pStruct->_getArray()->GetItem(pIndex->_getInt());
|
|
}
|
|
else
|
|
{
|
|
assert(pStruct->GetType() == C4V_PropList);
|
|
C4PropList *pPropList = pStruct->_getPropList();
|
|
if (!pPropList->GetPropertyByS(pIndex->_getStr(), pResult))
|
|
pResult->Set0();
|
|
}
|
|
// Remove index
|
|
PopValue();
|
|
break;
|
|
}
|
|
case AB_ARRAYA_SET:
|
|
{
|
|
C4Value *pValue = pCurVal, *pIndex = pCurVal - 1, *pStruct = pCurVal - 2, *pResult = pCurVal - 2;
|
|
// Typcheck to determine whether it's an array or a proplist
|
|
if(CheckArrayAccess(pStruct, pIndex) == C4V_Array)
|
|
{
|
|
if (pStruct->_getArray()->IsFrozen())
|
|
throw C4AulExecError("array write: array is readonly");
|
|
pStruct->_getArray()->SetItem(pIndex->_getInt(), *pValue);
|
|
}
|
|
else
|
|
{
|
|
assert(pStruct->GetType() == C4V_PropList);
|
|
C4PropList *pPropList = pStruct->_getPropList();
|
|
if (pPropList->IsFrozen())
|
|
throw C4AulExecError("proplist write: proplist is readonly");
|
|
pPropList->SetPropertyByS(pIndex->_getStr(), *pValue);
|
|
}
|
|
// Set result, remove array and index from stack
|
|
*pResult = *pValue;
|
|
PopValues(2);
|
|
break;
|
|
}
|
|
case AB_ARRAY_SLICE:
|
|
{
|
|
C4Value &Array = pCurVal[-2];
|
|
C4Value &StartIndex = pCurVal[-1];
|
|
C4Value &EndIndex = pCurVal[0];
|
|
|
|
// Typcheck
|
|
if (!Array.CheckConversion(C4V_Array))
|
|
throw C4AulExecError(FormatString("array slice: can't access %s as an array", Array.GetTypeName()).getData());
|
|
if (!StartIndex.CheckConversion(C4V_Int))
|
|
throw C4AulExecError(FormatString("array slice: start index of type %s, int expected", StartIndex.GetTypeName()).getData());
|
|
if (!EndIndex.CheckConversion(C4V_Int))
|
|
throw C4AulExecError(FormatString("array slice: end index of type %s, int expected", EndIndex.GetTypeName()).getData());
|
|
|
|
Array.SetArray(Array.GetData().Array->GetSlice(StartIndex._getInt(), EndIndex._getInt()));
|
|
|
|
// Remove both indices
|
|
PopValues(2);
|
|
break;
|
|
}
|
|
|
|
case AB_ARRAY_SLICE_SET:
|
|
{
|
|
C4Value &Array = pCurVal[-3];
|
|
C4Value &StartIndex = pCurVal[-2];
|
|
C4Value &EndIndex = pCurVal[-1];
|
|
C4Value &Value = pCurVal[0];
|
|
|
|
// Typcheck
|
|
if (!Array.CheckConversion(C4V_Array))
|
|
throw C4AulExecError(FormatString("array slice: can't access %s as an array", Array.GetTypeName()).getData());
|
|
if (!StartIndex.CheckConversion(C4V_Int))
|
|
throw C4AulExecError(FormatString("array slice: start index of type %s, int expected", StartIndex.GetTypeName()).getData());
|
|
if (!EndIndex.CheckConversion(C4V_Int))
|
|
throw C4AulExecError(FormatString("array slice: end index of type %s, int expected", EndIndex.GetTypeName()).getData());
|
|
|
|
C4ValueArray *pArray = Array._getArray();
|
|
if (pArray->IsFrozen()) throw C4AulExecError("array write: array is readonly");
|
|
pArray->SetSlice(StartIndex._getInt(), EndIndex._getInt(), Value);
|
|
|
|
// Set value as result, remove both indices and first copy of value
|
|
Array = Value;
|
|
PopValues(3);
|
|
break;
|
|
}
|
|
|
|
case AB_STACK:
|
|
if (pCPos->Par.i < 0)
|
|
PopValues(-pCPos->Par.i);
|
|
else
|
|
PushNullVals(pCPos->Par.i);
|
|
break;
|
|
|
|
case AB_JUMP:
|
|
fJump = true;
|
|
pCPos += pCPos->Par.i;
|
|
break;
|
|
|
|
case AB_JUMPAND:
|
|
if (!pCurVal[0])
|
|
{
|
|
fJump = true;
|
|
pCPos += pCPos->Par.i;
|
|
}
|
|
else
|
|
{
|
|
PopValue();
|
|
}
|
|
break;
|
|
|
|
case AB_JUMPOR:
|
|
if (!!pCurVal[0])
|
|
{
|
|
fJump = true;
|
|
pCPos += pCPos->Par.i;
|
|
}
|
|
else
|
|
{
|
|
PopValue();
|
|
}
|
|
break;
|
|
|
|
case AB_JUMPNNIL: // ??
|
|
{
|
|
if (pCurVal[0].GetType() != C4V_Nil)
|
|
{
|
|
fJump = true;
|
|
pCPos += pCPos->Par.i;
|
|
}
|
|
else
|
|
{
|
|
PopValue();
|
|
}
|
|
break;
|
|
}
|
|
|
|
case AB_CONDN:
|
|
if (!pCurVal[0])
|
|
{
|
|
fJump = true;
|
|
pCPos += pCPos->Par.i;
|
|
}
|
|
PopValue();
|
|
break;
|
|
|
|
case AB_COND:
|
|
if (pCurVal[0])
|
|
{
|
|
fJump = true;
|
|
pCPos += pCPos->Par.i;
|
|
}
|
|
PopValue();
|
|
break;
|
|
|
|
case AB_RETURN:
|
|
{
|
|
// Trace
|
|
if (iTraceStart >= 0)
|
|
{
|
|
StdStrBuf Buf("T");
|
|
Buf.AppendChars('>', ContextStackSize() - iTraceStart);
|
|
LogF("%s%s returned %s", Buf.getData(), pCurCtx->Func->GetName(), pCurVal->GetDataString().getData());
|
|
}
|
|
|
|
C4Value *pReturn = pCurCtx->Return;
|
|
|
|
// External call?
|
|
if (!pReturn)
|
|
{
|
|
// Get return value and stop executing.
|
|
C4Value rVal = *pCurVal;
|
|
PopValuesUntil(pCurCtx->Pars - 1);
|
|
PopContext();
|
|
return rVal;
|
|
}
|
|
|
|
// Save return value
|
|
if (pCurVal != pReturn)
|
|
pReturn->Set(*pCurVal);
|
|
|
|
// Pop context
|
|
PopContext();
|
|
|
|
// Clear value stack, except return value
|
|
PopValuesUntil(pReturn);
|
|
|
|
// Jump back, continue.
|
|
pCPos = pCurCtx->CPos + 1;
|
|
fJump = true;
|
|
|
|
break;
|
|
}
|
|
|
|
case AB_FUNC:
|
|
{
|
|
// Get function call data
|
|
C4AulFunc *pFunc = pCPos->Par.f;
|
|
C4Value *pPars = pCurVal - pFunc->GetParCount() + 1;
|
|
// Save current position
|
|
pCurCtx->CPos = pCPos;
|
|
assert(pCurCtx->Func->GetCode() <= pCPos);
|
|
// Do the call
|
|
C4AulBCC *pJump = Call(pFunc, pPars, pPars, nullptr);
|
|
if (pJump)
|
|
{
|
|
pCPos = pJump;
|
|
fJump = true;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case AB_PAR:
|
|
if (!pCurVal->CheckConversion(C4V_Int))
|
|
throw C4AulExecError(FormatString("Par: index of type %s, int expected", pCurVal->GetTypeName()).getData());
|
|
// Push reference to parameter on the stack
|
|
if (pCurVal->_getInt() >= 0 && pCurVal->_getInt() < pCurCtx->Func->GetParCount())
|
|
pCurVal->Set(pCurCtx->Pars[pCurVal->_getInt()]);
|
|
else
|
|
pCurVal->Set0();
|
|
break;
|
|
|
|
case AB_THIS:
|
|
if (!pCurCtx->Obj || !pCurCtx->Obj->Status)
|
|
PushNullVals(1);
|
|
else
|
|
PushPropList(pCurCtx->Obj);
|
|
break;
|
|
|
|
case AB_FOREACH_NEXT:
|
|
{
|
|
// This should always hold
|
|
assert(pCurVal->CheckConversion(C4V_Int));
|
|
int iItem = pCurVal->_getInt();
|
|
// Check array the first time only
|
|
if (!iItem)
|
|
{
|
|
if (!pCurVal[-1].CheckConversion(C4V_Array))
|
|
throw C4AulExecError(FormatString("for: array expected, but got %s", pCurVal[-1].GetTypeName()).getData());
|
|
}
|
|
C4ValueArray *pArray = pCurVal[-1]._getArray();
|
|
// No more entries?
|
|
if (pCurVal->_getInt() >= pArray->GetSize())
|
|
break;
|
|
// Get next
|
|
pCurVal[pCPos->Par.i] = pArray->GetItem(iItem);
|
|
// Save position
|
|
pCurVal->SetInt(iItem + 1);
|
|
// Jump over next instruction
|
|
pCPos += 2;
|
|
fJump = true;
|
|
break;
|
|
}
|
|
|
|
case AB_CALL:
|
|
case AB_CALLFS:
|
|
{
|
|
|
|
C4Value *pPars = pCurVal - C4AUL_MAX_Par + 1;
|
|
C4Value *pTargetVal = pCurVal - C4AUL_MAX_Par;
|
|
|
|
C4PropList *pDest;
|
|
if (pTargetVal->CheckConversion(C4V_PropList))
|
|
{
|
|
pDest = pTargetVal->_getPropList();
|
|
}
|
|
else
|
|
throw C4AulExecError(FormatString("'->': invalid target type %s, expected proplist", pTargetVal->GetTypeName()).getData());
|
|
|
|
// Search function for given context
|
|
C4AulFunc * pFunc = pDest->GetFunc(pCPos->Par.s);
|
|
if (!pFunc && pCPos->bccType == AB_CALLFS)
|
|
{
|
|
PopValuesUntil(pTargetVal);
|
|
pTargetVal->Set0();
|
|
break;
|
|
}
|
|
|
|
// Function not found?
|
|
if (!pFunc)
|
|
throw C4AulExecError(FormatString(R"('->': no function "%s" in object "%s")", pCPos->Par.s->GetCStr(), pTargetVal->GetDataString().getData()).getData());
|
|
|
|
// Save current position
|
|
pCurCtx->CPos = pCPos;
|
|
assert(pCurCtx->Func->GetCode() <= pCPos);
|
|
|
|
// adjust parameter count
|
|
if (pCurVal + 1 - pPars > pFunc->GetParCount())
|
|
PopValues(pCurVal + 1 - pPars - pFunc->GetParCount());
|
|
else
|
|
PushNullVals(pFunc->GetParCount() - (pCurVal + 1 - pPars));
|
|
|
|
// Call function
|
|
C4AulBCC *pNewCPos = Call(pFunc, pTargetVal, pPars, pDest);
|
|
if (pNewCPos)
|
|
{
|
|
// Jump
|
|
pCPos = pNewCPos;
|
|
fJump = true;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case AB_DEBUG:
|
|
#ifndef NOAULDEBUG
|
|
if (C4AulDebug *pDebug = C4AulDebug::GetDebugger())
|
|
pDebug->DebugStep(pCPos, pCurVal);
|
|
#endif
|
|
break;
|
|
}
|
|
|
|
// Continue
|
|
if (!fJump)
|
|
pCPos++;
|
|
}
|
|
|
|
}
|
|
catch (C4AulError &)
|
|
{
|
|
// Save current position
|
|
assert(pCurCtx->Func->GetCode() <= pCPos);
|
|
pCurCtx->CPos = pCPos;
|
|
throw;
|
|
}
|
|
}
|
|
|
|
C4AulBCC *C4AulExec::Call(C4AulFunc *pFunc, C4Value *pReturn, C4Value *pPars, C4PropList *pContext)
|
|
{
|
|
// No object given? Use current context
|
|
if (!pContext)
|
|
{
|
|
assert(pCurCtx >= Contexts);
|
|
pContext = pCurCtx->Obj;
|
|
}
|
|
|
|
pFunc->CheckParTypes(pPars, true);
|
|
|
|
// Script function?
|
|
C4AulScriptFunc *pSFunc = pFunc->SFunc();
|
|
if (pSFunc)
|
|
{
|
|
// Push a new context
|
|
C4AulScriptContext ctx;
|
|
ctx.Obj = pContext;
|
|
if (ctx.Obj && !ctx.Obj->Status)
|
|
throw C4AulExecError("using removed object");
|
|
ctx.Return = pReturn;
|
|
ctx.Pars = pPars;
|
|
ctx.Func = pSFunc;
|
|
ctx.CPos = nullptr;
|
|
PushContext(ctx);
|
|
|
|
// Jump to code
|
|
return pSFunc->GetCode();
|
|
}
|
|
else
|
|
{
|
|
if (pContext && !pContext->Status)
|
|
throw C4AulExecError("using removed object");
|
|
|
|
if (DEBUGREC_SCRIPT && Config.General.DebugRec)
|
|
{
|
|
StdStrBuf sCallText;
|
|
if (pContext && pContext->GetObject())
|
|
sCallText.AppendFormat("Object(%d): ", pContext->GetObject()->Number);
|
|
sCallText.Append(pFunc->GetName());
|
|
sCallText.AppendChar('(');
|
|
for (int i=0; i<C4AUL_MAX_Par; ++i)
|
|
{
|
|
if (i) sCallText.AppendChar(',');
|
|
C4Value &rV = pPars[i];
|
|
if (rV.GetType() == C4V_String)
|
|
{
|
|
C4String *s = rV.getStr();
|
|
if (!s)
|
|
sCallText.Append("(Snull)");
|
|
else
|
|
{
|
|
sCallText.Append(R"(")");
|
|
sCallText.Append(s->GetData());
|
|
sCallText.Append(R"(")");
|
|
}
|
|
}
|
|
else
|
|
sCallText.Append(rV.GetDataString());
|
|
}
|
|
sCallText.AppendChar(')');
|
|
sCallText.AppendChar(';');
|
|
AddDbgRec(RCT_AulFunc, sCallText.getData(), sCallText.getLength()+1);
|
|
}
|
|
|
|
// Execute
|
|
#ifdef _DEBUG
|
|
C4AulScriptContext *pCtx = pCurCtx;
|
|
#endif
|
|
if (pReturn > pCurVal)
|
|
PushValue(pFunc->Exec(pContext, pPars, true));
|
|
else
|
|
pReturn->Set(pFunc->Exec(pContext, pPars, true));
|
|
#ifdef _DEBUG
|
|
assert(pCtx == pCurCtx);
|
|
#endif
|
|
|
|
// Remove parameters from stack
|
|
PopValuesUntil(pReturn);
|
|
|
|
// Continue
|
|
return nullptr;
|
|
}
|
|
|
|
}
|
|
|
|
void C4AulExec::StartTrace()
|
|
{
|
|
if (iTraceStart < 0)
|
|
iTraceStart = ContextStackSize();
|
|
}
|
|
|
|
void C4AulExec::StartProfiling(C4ScriptHost *pProfiledScript)
|
|
{
|
|
// stop previous profiler run
|
|
if (fProfiling) StopProfiling();
|
|
fProfiling = true;
|
|
// resets profling times and starts recording the times
|
|
this->pProfiledScript = pProfiledScript;
|
|
C4TimeMilliseconds tNow = C4TimeMilliseconds::Now();
|
|
tDirectExecStart = tNow; // in case profiling is started from DirectExec
|
|
tDirectExecTotal = 0;
|
|
for (C4AulScriptContext *pCtx = Contexts; pCtx <= pCurCtx; ++pCtx)
|
|
pCtx->tTime = tNow;
|
|
}
|
|
|
|
void C4AulExec::PushContext(const C4AulScriptContext &rContext)
|
|
{
|
|
if (pCurCtx >= Contexts + MAX_CONTEXT_STACK - 1)
|
|
throw C4AulExecError("context stack overflow");
|
|
*++pCurCtx = rContext;
|
|
// Trace?
|
|
if (iTraceStart >= 0)
|
|
{
|
|
StdStrBuf Buf("T");
|
|
Buf.AppendChars('>', ContextStackSize() - iTraceStart);
|
|
pCurCtx->dump(Buf);
|
|
}
|
|
// Profiler: Safe time to measure difference afterwards
|
|
if (fProfiling) pCurCtx->tTime = C4TimeMilliseconds::Now();
|
|
}
|
|
|
|
void C4AulExec::PopContext()
|
|
{
|
|
if (pCurCtx < Contexts)
|
|
throw C4AulExecError("internal error: context stack underflow");
|
|
// Profiler adding up times
|
|
if (fProfiling)
|
|
{
|
|
uint32_t dt = C4TimeMilliseconds::Now() - pCurCtx->tTime;
|
|
if (pCurCtx->Func)
|
|
pCurCtx->Func->tProfileTime += dt;
|
|
}
|
|
// Trace done?
|
|
if (iTraceStart >= 0)
|
|
{
|
|
if (ContextStackSize() <= iTraceStart)
|
|
{
|
|
iTraceStart = -1;
|
|
}
|
|
}
|
|
pCurCtx--;
|
|
}
|
|
|
|
void C4AulProfiler::StartProfiling(C4ScriptHost *pScript)
|
|
{
|
|
AulExec.StartProfiling(pScript);
|
|
if(pScript)
|
|
ResetTimes(pScript->GetPropList());
|
|
else
|
|
ResetTimes();
|
|
}
|
|
|
|
void C4AulProfiler::StopProfiling()
|
|
{
|
|
if (!AulExec.IsProfiling()) return;
|
|
AulExec.StopProfiling();
|
|
// collect profiler times
|
|
C4AulProfiler Profiler;
|
|
Profiler.CollectEntry(nullptr, AulExec.tDirectExecTotal);
|
|
if(AulExec.pProfiledScript)
|
|
Profiler.CollectTimes(AulExec.pProfiledScript->GetPropList());
|
|
else
|
|
Profiler.CollectTimes();
|
|
Profiler.Show();
|
|
}
|
|
|
|
void C4AulProfiler::CollectEntry(C4AulScriptFunc *pFunc, uint32_t tProfileTime)
|
|
{
|
|
// zero entries are not collected to have a cleaner list
|
|
if (!tProfileTime) return;
|
|
// add entry to list
|
|
Entry e;
|
|
e.pFunc = pFunc;
|
|
e.tProfileTime = tProfileTime;
|
|
Times.push_back(e);
|
|
}
|
|
|
|
void C4AulProfiler::Show()
|
|
{
|
|
// sort by time
|
|
std::sort(Times.rbegin(), Times.rend());
|
|
// display them
|
|
Log("Profiler statistics:");
|
|
Log("==============================");
|
|
typedef std::vector<Entry> EntryList;
|
|
for (auto & e : Times)
|
|
{
|
|
LogF("%05ums\t%s", e.tProfileTime, e.pFunc ? (e.pFunc->GetFullName().getData()) : "Direct exec");
|
|
}
|
|
Log("==============================");
|
|
// done!
|
|
}
|
|
|
|
C4Value C4AulExec::DirectExec(C4PropList *p, const char *szScript, const char *szContext, bool fPassErrors, C4AulScriptContext* context, bool parse_function)
|
|
{
|
|
if (DEBUGREC_SCRIPT && Config.General.DebugRec)
|
|
{
|
|
AddDbgRec(RCT_DirectExec, szScript, strlen(szScript)+1);
|
|
int32_t iObjNumber = p && p->GetPropListNumbered() ? p->GetPropListNumbered()->Number : -1;
|
|
AddDbgRec(RCT_DirectExec, &iObjNumber, sizeof(int32_t));
|
|
}
|
|
// profiler
|
|
StartDirectExec();
|
|
C4PropListStatic * script = ::GameScript.GetPropList();
|
|
if (p && p->IsStatic())
|
|
script = p->IsStatic();
|
|
else if (p && p->GetDef())
|
|
script = p->GetDef();
|
|
// Add a new function
|
|
auto pFunc = std::make_unique<C4AulScriptFunc>(script, nullptr, nullptr, szScript);
|
|
// Parse function
|
|
try
|
|
{
|
|
if (parse_function)
|
|
{
|
|
// Expect a full function (e.g. "func foo() { return bar(); }")
|
|
pFunc->ParseDirectExecFunc(&::ScriptEngine, context);
|
|
}
|
|
else
|
|
{
|
|
// Expect a single statement (e.g. "bar()")
|
|
pFunc->ParseDirectExecStatement(&::ScriptEngine, context);
|
|
}
|
|
C4AulParSet Pars;
|
|
C4Value vRetVal(Exec(pFunc.get(), p, Pars.Par, fPassErrors));
|
|
// profiler
|
|
StopDirectExec();
|
|
return vRetVal;
|
|
}
|
|
catch (C4AulError &ex)
|
|
{
|
|
if(fPassErrors)
|
|
throw;
|
|
::ScriptEngine.GetErrorHandler()->OnError(ex.what());
|
|
LogCallStack();
|
|
StopDirectExec();
|
|
return C4VNull;
|
|
}
|
|
}
|
|
|
|
void C4AulProfiler::ResetTimes(C4PropListStatic * p)
|
|
{
|
|
// zero all profiler times of owned functions
|
|
C4AulScriptFunc *pSFunc;
|
|
for (C4String *pFn = p->EnumerateOwnFuncs(); pFn; pFn = p->EnumerateOwnFuncs(pFn))
|
|
if ((pSFunc = p->GetFunc(pFn)->SFunc()))
|
|
pSFunc->tProfileTime = 0;
|
|
}
|
|
|
|
void C4AulProfiler::CollectTimes(C4PropListStatic * p)
|
|
{
|
|
// collect all profiler times of owned functions
|
|
C4AulScriptFunc *pSFunc;
|
|
for (C4String *pFn = p->EnumerateOwnFuncs(); pFn; pFn = p->EnumerateOwnFuncs(pFn))
|
|
if ((pSFunc = p->GetFunc(pFn)->SFunc()))
|
|
CollectEntry(pSFunc, pSFunc->tProfileTime);
|
|
}
|
|
|
|
void C4AulProfiler::ResetTimes()
|
|
{
|
|
// zero all profiler times of owned functions
|
|
ResetTimes(::ScriptEngine.GetPropList());
|
|
// reset sub-scripts
|
|
for (C4ScriptHost *pScript = ::ScriptEngine.Child0; pScript; pScript = pScript->Next)
|
|
ResetTimes(pScript->GetPropList());
|
|
}
|
|
|
|
void C4AulProfiler::CollectTimes()
|
|
{
|
|
// collect all profiler times of owned functions
|
|
CollectTimes(::ScriptEngine.GetPropList());
|
|
// collect sub-scripts
|
|
for (C4ScriptHost *pScript = ::ScriptEngine.Child0; pScript; pScript = pScript->Next)
|
|
CollectTimes(pScript->GetPropList());
|
|
}
|