openclonk/engine/src/C4AulExec.cpp

1388 lines
35 KiB
C++

/*
* OpenClonk, http://www.openclonk.org
*
* Copyright (c) 2001-2002, 2006-2007 Sven Eberhardt
* Copyright (c) 2001-2002, 2005-2007 Peter Wortmann
* Copyright (c) 2006-2009 Günther Brammer
* Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de
*
* Portions might be copyrighted by other authors who have contributed
* to OpenClonk.
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
* See isc_license.txt for full license and disclaimer.
*
* "Clonk" is a registered trademark of Matthes Bender.
* See clonk_trademark_license.txt for full license.
*/
// executes script functions
#include <C4Include.h>
#include <C4Aul.h>
#ifndef BIG_C4INCLUDE
#include <C4Object.h>
#include <C4Config.h>
#include <C4GameMessage.h>
#include <C4Wrappers.h>
#endif
C4AulExecError::C4AulExecError(C4Object *pObj, const char *szError) : cObj(pObj)
{
// direct error message string
sMessage.Format("ERROR: %s", szError ? szError : "(no error message)");
}
void C4AulExecError::show()
{
#ifdef C4ENGINE
// log
C4AulError::show();
// debug mode object message
if (Game.DebugMode)
if (cObj)
::Messages.New(C4GM_Target,sMessage,cObj,NO_OWNER);
else
::Messages.New(C4GM_Global,sMessage,NULL,ANY_OWNER);
#endif
}
const int MAX_CONTEXT_STACK = 512;
const int MAX_VALUE_STACK = 1024;
void C4AulScriptContext::dump(StdStrBuf Dump)
{
bool fDirectExec = !*Func->Name;
if(!fDirectExec)
{
// Function name
Dump.Append(Func->Name);
// Parameters
Dump.AppendChar('(');
int iNullPars = 0;
for(int i = 0; i < C4AUL_MAX_Par; i++)
if(Pars + i < Vars)
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->Owner->ScriptName);
// Context
if(Obj)
Dump.AppendFormat(" (obj %s)", C4VObj(Obj).GetDataString().getData());
else if(Func->Owner->Def != NULL)
Dump.AppendFormat(" (def %s)", Func->Owner->Def->Name.getData());
// Script
if(!fDirectExec && Func->Owner)
Dump.AppendFormat(" (%s:%d)",
Func->pOrgScript->ScriptName.getData(),
SGetLine(Func->pOrgScript->GetScript(), CPos ? CPos->SPos : Func->Script));
// Log it
DebugLog(Dump.getData());
}
class C4AulExec
{
public:
C4AulExec()
: pCurCtx(Contexts - 1), pCurVal(Values - 1), iTraceStart(-1)
{ }
private:
C4AulScriptContext Contexts[MAX_CONTEXT_STACK];
C4Value Values[MAX_VALUE_STACK];
C4AulScriptContext *pCurCtx;
C4Value *pCurVal;
int iTraceStart;
bool fProfiling;
time_t tDirectExecStart, tDirectExecTotal; // profiler time for DirectExec
C4AulScript *pProfiledScript;
public:
C4Value Exec(C4AulScriptFunc *pSFunc, C4Object *pObj, C4Value pPars[], bool fPassErrors, bool fTemporaryScript = false);
C4Value Exec(C4AulBCC *pCPos, bool fPassErrors);
void StartTrace();
void StartProfiling(C4AulScript *pScript); // resets profling times and starts recording the times
void StopProfiling(); // stop the profiler and displays results
void AbortProfiling() { fProfiling=false; }
inline void StartDirectExec() { if (fProfiling) tDirectExecStart = timeGetTime(); }
inline void StopDirectExec() { if (fProfiling) tDirectExecTotal += timeGetTime() - tDirectExecStart; }
private:
void PushContext(const C4AulScriptContext &rContext)
{
if(pCurCtx >= Contexts + MAX_CONTEXT_STACK - 1)
throw new C4AulExecError(pCurCtx->Obj, "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 = timeGetTime();
}
void PopContext()
{
if(pCurCtx < Contexts)
throw new C4AulExecError(pCurCtx->Obj, "context stack underflow!");
// Profiler adding up times
if (fProfiling)
{
time_t dt = timeGetTime() - pCurCtx->tTime;
if (dt && pCurCtx->Func)
pCurCtx->Func->tProfileTime += dt;
}
// Trace done?
if(iTraceStart >= 0)
{
if(ContextStackSize() <= iTraceStart)
{
iTraceStart = -1;
}
}
if(pCurCtx->TemporaryScript)
delete pCurCtx->Func->Owner;
pCurCtx--;
}
void CheckOverflow(int iCnt)
{
if(ValueStackSize() + iCnt > MAX_VALUE_STACK)
throw new C4AulExecError(pCurCtx->Obj, "internal error: value stack overflow!");
}
void PushString(C4String * Str)
{
CheckOverflow(1);
(++pCurVal)->SetString(Str);
}
void PushArray(C4ValueArray * Array)
{
CheckOverflow(1);
(++pCurVal)->SetArray(Array);
}
void PushValue(const C4Value &rVal)
{
CheckOverflow(1);
(++pCurVal)->Set(rVal);
}
void PushValueRef(C4Value &rVal)
{
CheckOverflow(1);
(++pCurVal)->SetRef(&rVal);
}
void PushNullVals(int iCnt)
{
CheckOverflow(iCnt);
pCurVal += iCnt;
}
bool PopValue()
{
if(LocalValueStackSize() < 1)
throw new C4AulExecError(pCurCtx->Obj, "internal error: value stack underflow!");
(pCurVal--)->Set0();
return true;
}
void PopValues(int n)
{
if(LocalValueStackSize() < n)
throw new C4AulExecError(pCurCtx->Obj, "internal error: value stack underflow!");
while(n--)
(pCurVal--)->Set0();
}
void PopValuesUntil(C4Value *pUntilVal)
{
if(pUntilVal < Values - 1)
throw new C4AulExecError(pCurCtx->Obj, "internal error: value stack underflow!");
while(pCurVal > pUntilVal)
(pCurVal--)->Set0();
}
int ContextStackSize() const
{
return pCurCtx - Contexts + 1;
}
int ValueStackSize() const
{
return pCurVal - Values + 1;
}
int LocalValueStackSize() const
{
return ContextStackSize()
? pCurVal - pCurCtx->Vars - pCurCtx->Func->VarNamed.iSize + 1
: pCurVal - Values + 1;
}
void CheckOpPars(int iOpID)
{
// Get parameters
C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
// Typecheck parameters
if(!pPar1->ConvertTo(C4ScriptOpMap[iOpID].Type1))
throw new C4AulExecError(pCurCtx->Obj,
FormatString("operator \"%s\" left side: got \"%s\", but expected \"%s\"!",
C4ScriptOpMap[iOpID].Identifier, pPar1->GetTypeInfo(), GetC4VName(C4ScriptOpMap[iOpID].Type1)).getData());
if(!pPar2->ConvertTo(C4ScriptOpMap[iOpID].Type2))
throw new C4AulExecError(pCurCtx->Obj,
FormatString("operator \"%s\" right side: got \"%s\", but expected \"%s\"!",
C4ScriptOpMap[iOpID].Identifier, pPar2->GetTypeInfo(), GetC4VName(C4ScriptOpMap[iOpID].Type2)).getData());
}
void CheckOpPar(int iOpID)
{
// Typecheck parameter
if(!pCurVal->ConvertTo(C4ScriptOpMap[iOpID].Type1))
throw new C4AulExecError(pCurCtx->Obj,
FormatString("operator \"%s\": got \"%s\", but expected \"%s\"!",
C4ScriptOpMap[iOpID].Identifier, pCurVal->GetTypeInfo(), GetC4VName(C4ScriptOpMap[iOpID].Type1)).getData());
}
C4AulBCC *Call(C4AulFunc *pFunc, C4Value *pReturn, C4Value *pPars, C4Object *pObj = NULL, C4Def *pDef = NULL);
};
C4AulExec AulExec;
C4Value C4AulExec::Exec(C4AulScriptFunc *pSFunc, C4Object *pObj, C4Value *pnPars, bool fPassErrors, bool fTemporaryScript)
{
// Push parameters
C4Value *pPars = pCurVal + 1;
if(pnPars)
for(int i = 0; i < C4AUL_MAX_Par; i++)
PushValue(pnPars[i]);
// Push variables
C4Value *pVars = pCurVal + 1;
PushNullVals(pSFunc->VarNamed.iSize);
// Derive definition context from function owner (legacy)
C4Def *pDef = pObj ? pObj->Def : pSFunc->Owner->Def;
// Executing function in right context?
// This must hold: The scripter might try to access local variables that don't exist!
assert(!pSFunc->Owner->Def || pDef == pSFunc->Owner->Def);
// Push a new context
C4AulScriptContext ctx;
ctx.Obj = pObj;
ctx.Def = pDef;
ctx.Return = NULL;
ctx.Pars = pPars;
ctx.Vars = pVars;
ctx.Func = pSFunc;
ctx.TemporaryScript = fTemporaryScript;
ctx.CPos = NULL;
ctx.Caller = NULL;
PushContext(ctx);
// Execute
return Exec(pSFunc->Code, fPassErrors);
}
C4Value C4AulExec::Exec(C4AulBCC *pCPos, bool fPassErrors)
{
// Save start context
C4AulScriptContext *pOldCtx = pCurCtx;
try
{
for(;;)
{
bool fJump = false;
switch(pCPos->bccType)
{
case AB_INT:
PushValue(C4VInt(pCPos->Par.i));
break;
case AB_BOOL:
PushValue(C4VBool(!! pCPos->Par.i));
break;
case AB_STRING:
PushString(pCPos->Par.s);
break;
case AB_C4ID:
PushValue(C4VID(pCPos->Par.i));
break;
case AB_EOFN:
throw new C4AulExecError(pCurCtx->Obj, "function didn't return");
case AB_ERR:
throw new C4AulExecError(pCurCtx->Obj, "syntax error: see previous parser error for details.");
case AB_PARN_R:
PushValueRef(pCurCtx->Pars[pCPos->Par.i]);
break;
case AB_PARN_V:
PushValue(pCurCtx->Pars[pCPos->Par.i]);
break;
case AB_VARN_R:
PushValueRef(pCurCtx->Vars[pCPos->Par.i]);
break;
case AB_VARN_V:
PushValue(pCurCtx->Vars[pCPos->Par.i]);
break;
case AB_LOCALN_R: case AB_LOCALN_V:
if(!pCurCtx->Obj)
throw new C4AulExecError(pCurCtx->Obj, "can't access local variables in a definition call!");
if(pCurCtx->Func->Owner->Def != pCurCtx->Def)
throw new C4AulExecError(pCurCtx->Obj, "can't access local variables after ChangeDef!");
if (pCPos->bccType == AB_LOCALN_R)
PushValueRef(*pCurCtx->Obj->LocalNamed.GetItem(pCPos->Par.i));
else
PushValue(*pCurCtx->Obj->LocalNamed.GetItem(pCPos->Par.i));
break;
case AB_GLOBALN_R:
PushValueRef(*::ScriptEngine.GlobalNamed.GetItem(pCPos->Par.i));
break;
case AB_GLOBALN_V:
PushValue(*::ScriptEngine.GlobalNamed.GetItem(pCPos->Par.i));
break;
// prefix
case AB_Inc1: // ++
CheckOpPar(pCPos->Par.i);
if(pCurVal->GetRefVal().ConvertTo(C4V_Int))
++(*pCurVal);
else
pCurVal->Set0();
break;
case AB_Dec1: // --
CheckOpPar(pCPos->Par.i);
if(pCurVal->GetRefVal().ConvertTo(C4V_Int))
--(*pCurVal);
else
pCurVal->Set0();
break;
case AB_BitNot: // ~
CheckOpPar(pCPos->Par.i);
pCurVal->SetInt(~pCurVal->_getInt());
break;
case AB_Not: // !
CheckOpPar(pCPos->Par.i);
pCurVal->SetBool(!pCurVal->_getRaw());
break;
case AB_Neg: // -
CheckOpPar(pCPos->Par.i);
pCurVal->SetInt(-pCurVal->_getInt());
break;
// postfix (whithout second statement)
case AB_Inc1_Postfix: // ++
CheckOpPar(pCPos->Par.i);
if(pCurVal->GetRefVal().ConvertTo(C4V_Int))
pCurVal->Set((*pCurVal)++);
else
pCurVal->Set0();
break;
case AB_Dec1_Postfix: // --
CheckOpPar(pCPos->Par.i);
if(pCurVal->GetRefVal().ConvertTo(C4V_Int))
pCurVal->Set((*pCurVal)--);
else
pCurVal->Set0();
break;
// postfix
case AB_Pow: // **
{
CheckOpPars(pCPos->Par.i);
C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
pPar1->SetInt(Pow(pPar1->_getInt(), pPar2->_getInt()));
PopValue();
break;
}
case AB_Div: // /
{
CheckOpPars(pCPos->Par.i);
C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
if(pPar2->_getInt())
pPar1->SetInt(pPar1->_getInt() / pPar2->_getInt());
else
pPar1->Set0();
PopValue();
break;
}
case AB_Mul: // *
{
CheckOpPars(pCPos->Par.i);
C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
pPar1->SetInt(pPar1->_getInt() * pPar2->_getInt());
PopValue();
break;
}
case AB_Mod: // %
{
CheckOpPars(pCPos->Par.i);
C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
if(pPar2->_getInt())
pPar1->SetInt(pPar1->_getInt() % pPar2->_getInt());
else
pPar1->Set0();
PopValue();
break;
}
case AB_Sub: // -
{
CheckOpPars(pCPos->Par.i);
C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
pPar1->SetInt(pPar1->_getInt() - pPar2->_getInt());
PopValue();
break;
}
case AB_Sum: // +
{
CheckOpPars(pCPos->Par.i);
C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
pPar1->SetInt(pPar1->_getInt() + pPar2->_getInt());
PopValue();
break;
}
case AB_LeftShift: // <<
{
CheckOpPars(pCPos->Par.i);
C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
pPar1->SetInt(pPar1->_getInt() << pPar2->_getInt());
PopValue();
break;
}
case AB_RightShift: // >>
{
CheckOpPars(pCPos->Par.i);
C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
pPar1->SetInt(pPar1->_getInt() >> pPar2->_getInt());
PopValue();
break;
}
case AB_LessThan: // <
{
CheckOpPars(pCPos->Par.i);
C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
pPar1->SetBool(pPar1->_getInt() < pPar2->_getInt());
PopValue();
break;
}
case AB_LessThanEqual: // <=
{
CheckOpPars(pCPos->Par.i);
C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
pPar1->SetBool(pPar1->_getInt() <= pPar2->_getInt());
PopValue();
break;
}
case AB_GreaterThan: // >
{
CheckOpPars(pCPos->Par.i);
C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
pPar1->SetBool(pPar1->_getInt() > pPar2->_getInt());
PopValue();
break;
}
case AB_GreaterThanEqual: // >=
{
CheckOpPars(pCPos->Par.i);
C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
pPar1->SetBool(pPar1->_getInt() >= pPar2->_getInt());
PopValue();
break;
}
case AB_EqualIdent: // old ==
{
CheckOpPars(pCPos->Par.i);
C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
pPar1->SetBool(pPar1->_getRaw() == pPar2->_getRaw());
PopValue();
break;
}
case AB_Equal: // new ==
{
CheckOpPars(pCPos->Par.i);
C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
pPar1->SetBool(*pPar1 == *pPar2);
PopValue();
break;
}
case AB_NotEqualIdent: // old !=
{
CheckOpPars(pCPos->Par.i);
C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
pPar1->SetBool(pPar1->_getRaw() != pPar2->_getRaw());
PopValue();
break;
}
case AB_NotEqual: // new !=
{
CheckOpPars(pCPos->Par.i);
C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
pPar1->SetBool(*pPar1 != *pPar2);
PopValue();
break;
}
case AB_SEqual: // S=, eq
{
CheckOpPars(pCPos->Par.i);
C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
pPar1->SetBool(SEqual(pPar1->_getStr() ? pPar1->_getStr()->GetCStr() : "",
pPar2->_getStr() ? pPar2->_getStr()->GetCStr() : ""));
PopValue();
break;
}
case AB_SNEqual: // ne
{
CheckOpPars(pCPos->Par.i);
C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
pPar1->SetBool(!SEqual(pPar1->_getStr() ? pPar1->_getStr()->GetCStr() : "",
pPar2->_getStr() ? pPar2->_getStr()->GetCStr() : ""));
PopValue();
break;
}
case AB_BitAnd: // &
{
CheckOpPars(pCPos->Par.i);
C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
pPar1->SetInt(pPar1->_getInt() & pPar2->_getInt());
PopValue();
break;
}
case AB_BitXOr: // ^
{
CheckOpPars(pCPos->Par.i);
C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
pPar1->SetInt(pPar1->_getInt() ^ pPar2->_getInt());
PopValue();
break;
}
case AB_BitOr: // |
{
CheckOpPars(pCPos->Par.i);
C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
pPar1->SetInt(pPar1->_getInt() | pPar2->_getInt());
PopValue();
break;
}
case AB_And: // &&
{
CheckOpPars(pCPos->Par.i);
C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
pPar1->SetBool(pPar1->_getRaw() && pPar2->_getRaw());
PopValue();
break;
}
case AB_Or: // ||
{
CheckOpPars(pCPos->Par.i);
C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
pPar1->SetBool(pPar1->_getRaw() || pPar2->_getRaw());
PopValue();
break;
}
case AB_MulIt: // *=
{
CheckOpPars(pCPos->Par.i);
C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
pPar1->GetData().Int *= pPar2 ->_getInt();
PopValue();
break;
}
case AB_DivIt: // /=
{
CheckOpPars(pCPos->Par.i);
C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
pPar1->GetData().Int = pPar2->_getInt() ? pPar1->GetData().Int / pPar2->_getInt() : 0;
PopValue();
break;
}
case AB_ModIt: // %=
{
CheckOpPars(pCPos->Par.i);
C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
pPar1->GetData().Int = pPar2->_getInt() ? pPar1->GetData().Int % pPar2->_getInt() : 0;
PopValue();
break;
}
case AB_Inc: // +=
{
CheckOpPars(pCPos->Par.i);
C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
pPar1->GetData().Int += pPar2 ->_getInt();
PopValue();
break;
}
case AB_Dec: // -=
{
CheckOpPars(pCPos->Par.i);
C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
pPar1->GetData().Int -= pPar2 ->_getInt();
PopValue();
break;
}
case AB_AndIt: // &=
{
CheckOpPars(pCPos->Par.i);
C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
pPar1->GetData().Int &= pPar2 ->_getInt();
PopValue();
break;
}
case AB_OrIt: // |=
{
CheckOpPars(pCPos->Par.i);
C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
pPar1->GetData().Int |= pPar2 ->_getInt();
PopValue();
break;
}
case AB_XOrIt: // ^=
{
CheckOpPars(pCPos->Par.i);
C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
pPar1->GetData().Int ^= pPar2 ->_getInt();
PopValue();
break;
}
case AB_Set: // =
{
CheckOpPars(pCPos->Par.i);
C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
*pPar1 = *pPar2;
PopValue();
break;
}
/* case AB_UNOP:
{
int iOpID = pCPos->Par.i;
// Typecheck parameter
if(!pCurVal->ConvertTo(C4ScriptOpMap[iOpID].Type1))
throw new C4AulExecError(pCurCtx->Obj,
FormatString("operator \"%s\": got \"%s\", but expected \"%s\"!",
C4ScriptOpMap[iOpID].Identifier, pCurVal->GetTypeInfo(), GetC4VName(C4ScriptOpMap[iOpID].Type1)).getData());
// Execute operator
if(C4ScriptOpMap[iOpID].Function)
pCurVal->Set((*C4ScriptOpMap[iOpID].Function)(pCurCtx, pCurVal->_getRaw(), 0), C4ScriptOpMap[iOpID].RetType);
else if(C4ScriptOpMap[iOpID].FunctionC4V)
pCurVal->Set((*C4ScriptOpMap[iOpID].FunctionC4V)(pCurCtx, pCurVal, NULL));
break;
}
case AB_BINOP:
{
int iOpID = pCPos->Par.i;
// Get parameters
C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
// Typecheck parameters
if(!pPar1->ConvertTo(C4ScriptOpMap[iOpID].Type1))
throw new C4AulExecError(pCurCtx->Obj,
FormatString("operator \"%s\" left side: got \"%s\", but expected \"%s\"!",
C4ScriptOpMap[iOpID].Identifier, pPar1->GetTypeInfo(), GetC4VName(C4ScriptOpMap[iOpID].Type1)).getData());
if(!pPar2->ConvertTo(C4ScriptOpMap[iOpID].Type2))
throw new C4AulExecError(pCurCtx->Obj,
FormatString("operator \"%s\" right side: got \"%s\", but expected \"%s\"!",
C4ScriptOpMap[iOpID].Identifier, pPar2->GetTypeInfo(), GetC4VName(C4ScriptOpMap[iOpID].Type2)).getData());
// Execute operator
if(C4ScriptOpMap[iOpID].Function)
pPar1->Set((*C4ScriptOpMap[iOpID].Function)(pCurCtx, pPar1->_getRaw(), pPar2->_getRaw()),C4ScriptOpMap[iOpID].RetType);
else if(C4ScriptOpMap[iOpID].FunctionC4V)
pPar1->Set((*C4ScriptOpMap[iOpID].FunctionC4V)(pCurCtx, pPar1, pPar2));
// Pop second parameter
PopValue();
break;
}
*/
case AB_ARRAY:
{
// Create array
C4ValueArray *pArray = new C4ValueArray(pCPos->Par.i);
// Pop values from stack
for(int i = 0; i < pCPos->Par.i; i++)
pArray->GetItem(i) = pCurVal[i - pCPos->Par.i + 1];
// Push array
if(pCPos->Par.i > 0)
{
PopValues(pCPos->Par.i - 1);
pCurVal->SetArray(pArray);
}
else
PushArray(pArray);
break;
}
case AB_ARRAYA_R: case AB_ARRAYA_V:
{
C4Value &Index = pCurVal[0];
C4Value &Array = pCurVal[-1].GetRefVal();
// Typcheck
if(!Array.ConvertTo(C4V_Array))
throw new C4AulExecError(pCurCtx->Obj, FormatString("array access: can't access %s as an array!", Array.GetTypeName()).getData());
if(!Index.ConvertTo(C4V_Int))
throw new C4AulExecError(pCurCtx->Obj, FormatString("array access: index of type %s, int expected!", Index.GetTypeName()).getData());
// Set reference to array element
if (pCPos->bccType == AB_ARRAYA_R)
Array.GetArrayElement(Index._getInt(), pCurVal[-1], pCurCtx);
else
// do not mark array as having element references
Array.GetArrayElement(Index._getInt(), pCurVal[-1], pCurCtx, true);
// Remove index
PopValue();
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_CONDN:
if(!pCurVal[0])
{
fJump = true;
pCPos += pCPos->Par.i;
}
PopValue();
break;
case AB_RETURN:
{
// Resolve reference
if(!pCurCtx->Func->SFunc()->bReturnRef)
pCurVal->Deref();
// Trace
if(iTraceStart >= 0)
{
StdStrBuf Buf("T");
Buf.AppendChars('>', ContextStackSize() - iTraceStart);
LogF("%s%s returned %s", Buf.getData(), pCurCtx->Func->Name, pCurVal->GetDataString().getData());
}
// External call?
C4Value *pReturn = pCurCtx->Return;
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;
// Do the call
C4AulBCC *pJump = Call(pFunc, pPars, pPars, NULL);
if(pJump)
{
pCPos = pJump;
fJump = true;
}
break;
}
case AB_VAR_R: case AB_VAR_V:
if(!pCurVal->ConvertTo(C4V_Int))
throw new C4AulExecError(pCurCtx->Obj, FormatString("Var: index of type %s, int expected!", pCurVal->GetTypeName()).getData());
// Push reference to variable on the stack
if (pCPos->bccType == AB_VAR_R)
pCurVal->SetRef(&pCurCtx->NumVars.GetItem(pCurVal->_getInt()));
else
pCurVal->Set(pCurCtx->NumVars.GetItem(pCurVal->_getInt()));
break;
case AB_PAR_R: case AB_PAR_V:
if(!pCurVal->ConvertTo(C4V_Int))
throw new C4AulExecError(pCurCtx->Obj, 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->ParCnt())
{
if (pCPos->bccType == AB_PAR_R)
pCurVal->SetRef(&pCurCtx->Pars[pCurVal->_getInt()]);
else
pCurVal->Set(pCurCtx->Pars[pCurVal->_getInt()]);
}
else
pCurVal->Set0();
break;
case AB_FOREACH_NEXT:
{
// This should always hold
assert(pCurVal->ConvertTo(C4V_Int));
int iItem = pCurVal->_getInt();
// Check array the first time only
if(!iItem)
{
if(!pCurVal[-1].ConvertTo(C4V_Array))
throw new C4AulExecError(pCurCtx->Obj, FormatString("for: array expected, but got %s!", pCurVal[-1].GetTypeName()).getData());
if(!pCurVal[-1]._getArray())
throw new C4AulExecError(pCurCtx->Obj, FormatString("for: array expected, but got 0!").getData());
}
C4ValueArray *pArray = pCurVal[-1]._getArray();
// No more entries?
if(pCurVal->_getInt() >= pArray->GetSize())
break;
// Get next
pCurCtx->Vars[pCPos->Par.i] = pArray->GetItem(iItem);
// Save position
pCurVal->SetInt(iItem + 1);
// Jump over next instruction
pCPos += 2;
fJump = true;
break;
}
case AB_IVARN:
pCurCtx->Vars[pCPos->Par.i] = pCurVal[0];
PopValue();
break;
case AB_CALLNS:
// Ignore. TODO: Fix this.
break;
case AB_CALL:
case AB_CALLFS:
{
C4Value *pPars = pCurVal - C4AUL_MAX_Par + 1;
C4Value *pTargetVal = pCurVal - C4AUL_MAX_Par;
// Check for call to null
if(!*pTargetVal)
throw new C4AulExecError(pCurCtx->Obj, "Object call: target is zero!");
// Get call target - "object" or "id" are allowed
C4Object *pDestObj; C4Def *pDestDef;
if(pTargetVal->ConvertTo(C4V_C4Object))
{
// object call
pDestObj = pTargetVal->_getObj();
pDestDef = pDestObj->Def;
}
else if(pTargetVal->ConvertTo(C4V_C4ID))
{
// definition call
pDestObj = NULL;
pDestDef = C4Id2Def(pTargetVal->_getC4ID());
// definition must be known
if(!pDestDef)
throw new C4AulExecError(pCurCtx->Obj,
FormatString("Definition call: Definition for id %s not found!", C4IdText(pTargetVal->_getC4ID())).getData());
}
else
throw new C4AulExecError(pCurCtx->Obj,
FormatString("Object call: Invalid target type %s, expected object or id!", pTargetVal->GetTypeName()).getData());
// Search function for given context
const char * szFuncName = pCPos->Par.s->GetCStr();
C4AulFunc * pFunc = pDestDef->Script.GetFuncRecursive(szFuncName);
if(!pFunc && pCPos->bccType == AB_CALLFS)
{
PopValuesUntil(pTargetVal);
pTargetVal->Set0();
break;
}
// Function not found?
if(!pFunc)
{
if(pDestObj)
throw new C4AulExecError(pCurCtx->Obj,
FormatString("Object call: No function \"%s\" in object \"%s\"!", szFuncName, pTargetVal->GetDataString().getData()).getData());
else
throw new C4AulExecError(pCurCtx->Obj,
FormatString("Definition call: No function \"%s\" in definition \"%s\"!", szFuncName, pDestDef->Name.getData()).getData());
}
// Resolve overloads
while(pFunc->OverloadedBy)
pFunc = pFunc->OverloadedBy;
// Save current position
pCurCtx->CPos = pCPos;
// Call function
C4AulBCC *pNewCPos = Call(pFunc, pTargetVal, pPars, pDestObj, pDestDef);
if(pNewCPos)
{
// Jump
pCPos = pNewCPos;
fJump = true;
}
break;
}
default:
assert(false);
}
// Continue
if(!fJump)
pCPos++;
}
}
catch(C4AulError *e)
{
// Save current position
pOldCtx->CPos = pCPos;
// Pass?
if(fPassErrors)
throw;
// Show
e->show();
delete e;
// Trace
for (C4AulScriptContext *pCtx = pCurCtx; pCtx >= Contexts; pCtx--)
pCtx->dump(StdStrBuf(" by: "));
// Unwind stack
C4Value *pUntil = NULL;
while(pCurCtx >= pOldCtx)
{
pUntil = pCurCtx->Pars - 1;
PopContext();
}
if(pUntil)
PopValuesUntil(pUntil);
}
// Return nothing
return C4VNull;
}
C4AulBCC *C4AulExec::Call(C4AulFunc *pFunc, C4Value *pReturn, C4Value *pPars, C4Object *pObj, C4Def *pDef)
{
// No object given? Use current context
if(!pObj && !pDef)
{
assert(pCurCtx >= Contexts);
pObj = pCurCtx->Obj;
pDef = pCurCtx->Def;
}
// Convert parameters (typecheck)
C4V_Type *pTypes = pFunc->GetParType();
for(int i = 0; i < pFunc->GetParCount(); i++)
if(!pPars[i].ConvertTo(pTypes[i]))
throw new C4AulExecError(pCurCtx->Obj,
FormatString("call to \"%s\" parameter %d: got \"%s\", but expected \"%s\"!",
pFunc->Name, i + 1, pPars[i].GetTypeName(), GetC4VName(pTypes[i])
).getData());
// Script function?
C4AulScriptFunc *pSFunc = pFunc->SFunc();
if(pSFunc)
{
// Push variables
C4Value *pVars = pCurVal + 1;
PushNullVals(pSFunc->VarNamed.iSize);
// Check context
assert(!pSFunc->Owner->Def || pDef == pSFunc->Owner->Def);
// Push a new context
C4AulScriptContext ctx;
ctx.Obj = pObj;
ctx.Def = pDef;
ctx.Caller = pCurCtx;
ctx.Return = pReturn;
ctx.Pars = pPars;
ctx.Vars = pVars;
ctx.Func = pSFunc;
ctx.TemporaryScript = false;
ctx.CPos = NULL;
PushContext(ctx);
// Jump to code
return pSFunc->Code;
}
else
{
// Create new context
C4AulContext CallCtx;
CallCtx.Obj = pObj;
CallCtx.Def = pDef;
CallCtx.Caller = pCurCtx;
#ifdef DEBUGREC_SCRIPT
if (Game.FrameCounter >= DEBUGREC_START_FRAME)
{
StdStrBuf sCallText;
if (pObj)
sCallText.AppendFormat("Object(%d): ", pObj->Number);
sCallText.Append(pFunc->Name);
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("\"");
sCallText.Append(s->Data);
sCallText.Append("\"");
}
}
else
sCallText.Append(rV.GetDataString());
}
sCallText.AppendChar(')');
sCallText.AppendChar(';');
AddDbgRec(RCT_AulFunc, sCallText.getData(), sCallText.getLength()+1);
}
#endif
// Execute
#ifdef _DEBUG
C4AulScriptContext *pCtx = pCurCtx;
#endif
if(pReturn > pCurVal)
PushValue(pFunc->Exec(&CallCtx, pPars, true));
else
pReturn->Set(pFunc->Exec(&CallCtx, pPars, true));
#ifdef _DEBUG
assert(pCtx == pCurCtx);
#endif
// Remove parameters from stack
PopValuesUntil(pReturn);
// Continue
return NULL;
}
}
void C4AulStartTrace()
{
AulExec.StartTrace();
}
void C4AulExec::StartTrace()
{
if(iTraceStart < 0)
iTraceStart = ContextStackSize();
}
void C4AulExec::StartProfiling(C4AulScript *pProfiledScript)
{
// stop previous profiler run
if (fProfiling) AbortProfiling();
fProfiling = true;
// resets profling times and starts recording the times
this->pProfiledScript = pProfiledScript;
time_t tNow = timeGetTime();
tDirectExecStart = tNow; // in case profiling is started from DirectExec
tDirectExecTotal = 0;
pProfiledScript->ResetProfilerTimes();
for (C4AulScriptContext *pCtx = Contexts; pCtx <= pCurCtx; ++pCtx)
pCtx->tTime = tNow;
}
void C4AulExec::StopProfiling()
{
// stop the profiler and displays results
if (!fProfiling) return;
fProfiling = false;
// collect profiler times
C4AulProfiler Profiler;
Profiler.CollectEntry(NULL, tDirectExecTotal);
pProfiledScript->CollectProfilerTimes(Profiler);
Profiler.Show();
}
void C4AulProfiler::StartProfiling(C4AulScript *pScript)
{
AulExec.StartProfiling(pScript);
}
void C4AulProfiler::StopProfiling()
{
AulExec.StopProfiling();
}
void C4AulProfiler::Abort()
{
AulExec.AbortProfiling();
}
void C4AulProfiler::CollectEntry(C4AulScriptFunc *pFunc, time_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 (EntryList::iterator i = Times.begin(); i!=Times.end(); ++i)
{
Entry &e = (*i);
LogF("%05dms\t%s", (int) e.tProfileTime, e.pFunc ? (e.pFunc->GetFullName().getData()) : "Direct exec");
}
Log("==============================");
// done!
}
C4Value C4AulFunc::Exec(C4Object *pObj, C4AulParSet* pPars, bool fPassErrors)
{
// construct a dummy caller context
C4AulContext ctx;
ctx.Obj = pObj;
ctx.Def = pObj ? pObj->Def : NULL;
ctx.Caller = NULL;
// execute
return Exec(&ctx, pPars ? pPars->Par : C4AulParSet().Par, fPassErrors);
}
C4Value C4AulScriptFunc::Exec(C4AulContext *pCtx, C4Value pPars[], bool fPassErrors)
{
#ifdef C4ENGINE
// handle easiest case first
if (Owner->State != ASS_PARSED) return C4VNull;
// execute
return AulExec.Exec(this, pCtx->Obj, pPars, fPassErrors);
#else
return C4AulNull;
#endif
}
C4Value C4AulScriptFunc::Exec(C4Object *pObj, C4AulParSet *pPars, bool fPassErrors)
{
#ifdef C4ENGINE
// handle easiest case first
if (Owner->State != ASS_PARSED) return C4VNull;
// execute
return AulExec.Exec(this, pObj, pPars ? pPars->Par : C4AulParSet().Par, fPassErrors);
#else
return C4AulNull;
#endif
}
C4Value C4AulDefFunc::Exec(C4AulContext *pCallerCtx, C4Value pPars[], bool fPassErrors)
{
// Choose function call format to use
if(Def->FunctionC4V2 != 0)
// C4V function
return Def->FunctionC4V2(pCallerCtx, pPars);
if(Def->FunctionC4V != 0)
// C4V function
return Def->FunctionC4V(pCallerCtx, &pPars[0], &pPars[1], &pPars[2], &pPars[3], &pPars[4], &pPars[5], &pPars[6], &pPars[7], &pPars[8], &pPars[9]);
// should never happen...
return C4VNull;
}
C4Value C4AulScript::DirectExec(C4Object *pObj, const char *szScript, const char *szContext, bool fPassErrors, enum Strict Strict)
{
#ifdef DEBUGREC_SCRIPT
AddDbgRec(RCT_DirectExec, szScript, strlen(szScript)+1);
int32_t iObjNumber = pObj ? pObj->Number : -1;
AddDbgRec(RCT_DirectExec, &iObjNumber, sizeof(int32_t));
#endif
// profiler
AulExec.StartDirectExec();
// Create a new temporary script as child of this script
C4AulScript* pScript = new C4AulScript();
pScript->Script.Copy(szScript);
pScript->ScriptName = FormatString("%s in %s", szContext, ScriptName.getData());
pScript->Strict = Strict;
pScript->Temporary = true;
pScript->State = ASS_LINKED;
if (pObj)
{
pScript->Def = pObj->Def;
pScript->LocalNamed = pObj->Def->Script.LocalNamed;
}
else
{
pScript->Def = NULL;
}
pScript->Reg2List(Engine, this);
// Add a new function
C4AulScriptFunc *pFunc = new C4AulScriptFunc(pScript, "");
pFunc->Script = pScript->Script.getData();
pFunc->pOrgScript = pScript;
// Parse function
try
{
pScript->ParseFn(pFunc, true);
}
catch(C4AulError *ex)
{
ex->show();
delete ex;
delete pFunc;
delete pScript;
return C4VNull;
}
pFunc->Code = pScript->Code;
pScript->State = ASS_PARSED;
// Execute. The TemporaryScript-parameter makes sure the script will be deleted later on.
C4Value vRetVal(AulExec.Exec(pFunc, pObj, NULL, fPassErrors, true));
// profiler
AulExec.StopDirectExec();
return vRetVal;
}
void C4AulScript::ResetProfilerTimes()
{
// zero all profiler times of owned functions
C4AulScriptFunc *pSFunc;
for (C4AulFunc *pFn = Func0; pFn; pFn = pFn->Next)
if (pSFunc = pFn->SFunc())
pSFunc->tProfileTime = 0;
// reset sub-scripts
for (C4AulScript *pScript = Child0; pScript; pScript = pScript->Next)
pScript->ResetProfilerTimes();
}
void C4AulScript::CollectProfilerTimes(class C4AulProfiler &rProfiler)
{
// collect all profiler times of owned functions
C4AulScriptFunc *pSFunc;
for (C4AulFunc *pFn = Func0; pFn; pFn = pFn->Next)
if (pSFunc = pFn->SFunc())
rProfiler.CollectEntry(pSFunc, pSFunc->tProfileTime);
// collect sub-scripts
for (C4AulScript *pScript = Child0; pScript; pScript = pScript->Next)
pScript->CollectProfilerTimes(rProfiler);
}