// Ryzom - MMORPG Framework
// Copyright (C) 2010 Winch Gate Property Limited
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
#include "stdpch.h"
#include "script_vm.h"
#include "script_compiler.h"
using namespace std;
using namespace NLMISC;
namespace AIVM
{
//////////////////////////////////////////////////////////////////////////////
// Library management //
//////////////////////////////////////////////////////////////////////////////
CLibrary* CLibrary::_instance = NULL;
CLibrary::TLibContainer CLibrary::_libs;
#if !FINAL_VERSION
CVariable AIScriptDisplayPrint("aiscript", "AIScriptDisplayPrint", "Display the script 'print's in the AIS log.", true, 0, true);
#else
CVariable AIScriptDisplayPrint("aiscript", "AIScriptDisplayPrint", "Display the script 'print's in the AIS log.", false, 0, true);
#endif
CVariable AIScriptDisplayLog("aiscript", "AIScriptDisplayLog", "Display the script 'log's in the AIS log.", true, 0, true);
void CLibrary::addLib(std::string const& name, std::string const& code)
{
vector codeVect;
size_t start, end;
start = 0;
// Cut code in lines
while (start byteCode = AICOMP::CCompiler::getInstance().compileCode(codeVect, name);
if (byteCode!=NULL)
addLib(name, byteCode);
}
void CLibrary::addLib(std::string const& name, std::vector const& code)
{
NLMISC::CSmartPtr byteCode=AICOMP::CCompiler::getInstance().compileCode(code, name);
if (byteCode!=NULL)
addLib(name, byteCode);
}
void CLibrary::addLib(std::string const& name, NLMISC::CSmartPtr const& byteCode)
{
if (byteCode==NULL)
{
nlwarning("Trying to register an empty library script.");
return;
}
nlinfo("Adding script library %s", name.c_str());
_libs.insert(std::make_pair(name, byteCode));
}
//////////////////////////////////////////////////////////////////////////////
// Virtual machine //
//////////////////////////////////////////////////////////////////////////////
CSmartPtr CLibrary::getLib(std::string const& name)
{
TLibContainer::const_iterator it = _libs.find(name);
if (it!=_libs.end())
return it->second;
else
return CSmartPtr(NULL);
}
CScriptVM* CScriptVM::_Instance = NULL;
CScriptVM* CScriptVM::getInstance()
{
if (_Instance==NULL)
_Instance = new CScriptVM;
return _Instance;
}
void CScriptVM::destroyInstance()
{
if (_Instance!=NULL)
{
delete _Instance;
_Instance = NULL;
}
}
void CScriptVM::interpretCode(
IScriptContext* thisContext,
IScriptContext* parentContext,
IScriptContext* callerContext,
CByteCodeEntry const& codeScriptEntry)
{
NLMISC::CSmartPtr const& byteCode = codeScriptEntry.code();
size_t startIndex = codeScriptEntry.index();
if (byteCode.isNull())
return;
vector const& opcodes = byteCode->_opcodes;
static TStringId parentStrId = CStringMapper::map("parent");
static TStringId callerStrId = CStringMapper::map("caller");
CScriptStack stack;
size_t index = startIndex;
string currentString;
while (index < opcodes.size())
{
#if !FINAL_VERSION
EOpcode op = (EOpcode)opcodes[index];
#endif
switch (opcodes[index])
{
default:
case INVALID_OPCODE:
nlwarning("Invalid Opcode for Group '%s' with code in '%s'", thisContext->getContextName().c_str(), byteCode->_sourceName.c_str());
nlassert(false);
break;
case EOP:
return; // End Of Program
case EQ: // == Need: Value1: Value2 After: Value1==Value2 (Boolean as float)
{
const float res=stack.top(1)==stack.top()?1.f:0.f;
stack.pop();
stack.top()=res;
++index;
}
continue;
case NEQ: // != Need: Value1: Value2 After: Value1!=Value2 (Boolean as float)
{
const float res=stack.top(1)!=stack.top()?1.f:0.f;
stack.pop();
stack.top()=res;
index++;
}
continue;
case INF: // < Need: Value1: Value2 After: Value1 Need: Value1: Value2 After: Value1>Value2 (Boolean as float)
{
const float res=stack.top(1)>stack.top()?1.f:0.f;
stack.pop();
stack.top()=res;
index++;
}
continue;
case SUPEQ: // >= Need: Value1: Value2 After: Value1>=Value2 (Boolean as float)
{
const float res=stack.top(1)>=stack.top()?1.f:0.f;
stack.pop();
stack.top()=res;
index++;
}
continue;
case ADD: // + Need: Value1: Value2 After: Value1+Value2
{
CScriptStack::CStackEntry &entry0=stack.top();
CScriptStack::CStackEntry &entry1=stack.top(1);
nlassert((entry0.type()==CScriptStack::EFloat||entry0.type()==CScriptStack::EString)
&&(entry1.type()==CScriptStack::EFloat||entry1.type()==CScriptStack::EString));
breakable
{
if (entry0.type()==entry1.type())
{
if (entry0.type()==CScriptStack::EFloat)
(float&)entry1+=(float&)entry0;
else
(string&)entry1+=(string&)entry0;
}
else
{
if (entry0.type()==CScriptStack::EFloat)
(string&)entry1+=toString("%g", (float&)entry0);
else
{
const float value=entry1;
entry1=toString("%g", value);
(string&)entry1+=(string&)entry0;
}
}
}
stack.pop();
index++;
}
continue;
case SUB: // - Need: Value1: Value2 After: Value1-Value2
{
const float val=stack.top();
stack.pop();
(float&)stack.top()-=val;
index++;
}
continue;
case MUL: // * Need: Value1: Value2 After: Value1/Value2
{
float &res=stack.top(1);
res*=(float&)stack.top();
stack.pop();
index++;
}
continue;
case DIV: // / Need: Value1: Value2 After: Value1/Value2 !Exception Gestion.
{
float &res=stack.top(1);
const float &divisor=stack.top();
if (divisor==0)
res=1;
else
res/=divisor;
stack.pop();
index++;
}
continue;
case AND: // && Need: Value1: Value2 After: Value1&&Value2
{
const bool val1=(float&)stack.top(1)!=0.f;
const bool val2=(float&)stack.top()!=0.f;
stack.pop();
stack.top()=(val1&&val2)?1.f:0.f;
index++;
}
continue;
case OR: // || Need: Value1: Value2 After: Value1||Value2
{
const bool val1=(float&)stack.top(1)!=0.f;
const bool val2=(float&)stack.top()!=0.f;
stack.pop();
stack.top()=(val1||val2)?1.f:0.f;
index++;
}
continue;
case NOT: // ! Need: Value After: !Value
{
float &val=stack.top();
val=(val==0.f)?1.f:0.f;
index++;
}
continue;
case PUSH_ON_STACK: // Set a Value (float) Need: - After: Value(float)
{
stack.push(*((float*)&opcodes[index+1]));
index+=2;
}
continue;
case POP: // Pop Need: ValToPop After: -
{
stack.pop();
index++;
}
continue;
case SET_VAR_VAL: // Set a Value to a Var. Need: VarName: VarValue After: -
{
float f = 0.0f;
switch (stack.top().type())
{
case CScriptStack::EString:
{
string &str=stack.top();
f=(float)atof(str.c_str());
}
break;
case CScriptStack::EFloat:
{
f = (float&)stack.top();
}
break;
default:
nlwarning("Stack top type invalid, poping top value!");
}
stack.pop();
thisContext->setLogicVar(*((TStringId*)&opcodes[index+1]), f);
index+=2;
}
continue;
case SET_STR_VAR_VAL: // Set a Value to a Var. Need: VarName: VarValue After: -
{
switch (stack.top().type())
{
case CScriptStack::EString:
{
thisContext->setStrLogicVar(*((TStringId*)&opcodes[index+1]), stack.top());
}
break;
case CScriptStack::EFloat:
{
float const& val = stack.top();
thisContext->setStrLogicVar(*((TStringId*)&opcodes[index+1]),NLMISC::toString("%g", val));
}
break;
default:
nlwarning("Stack top type invalid, poping top value!");
thisContext->setStrLogicVar(*((TStringId*)&opcodes[index+1]),std::string());
}
stack.pop();
index+=2;
}
continue;
case SET_CTX_VAR_VAL: // Set a Value to a Var. Need: VarName: VarValue After: -
{
switch (stack.top().type())
{
case CScriptStack::EContext:
{
thisContext->setCtxLogicVar(*((TStringId*)&opcodes[index+1]), stack.top());
}
break;
default:
nlwarning("Stack top type invalid, poping top value!");
thisContext->setCtxLogicVar(*((TStringId*)&opcodes[index+1]), (IScriptContext*)0);
}
stack.pop();
index+=2;
}
continue;
case PUSH_VAR_VAL: // Push the Value of a Var. Need: - (VarName on next IP) After: VarValue(float)
{
const float f=thisContext->getLogicVar(*((TStringId*)&opcodes[index+1]));
stack.push(f);
index+=2;
}
continue;
case PUSH_STR_VAR_VAL: // Push the Value of a Var. Need: - (VarName on next IP) After: VarValue(float)
{
std::string str = thisContext->getStrLogicVar(*((TStringId*)&opcodes[index+1]));
stack.push(str);
index+=2;
}
continue;
case PUSH_CTX_VAR_VAL: // Push the Value of a Var. Need: - (VarName on next IP) After: VarValue(float)
{
IScriptContext* ctx = thisContext->getCtxLogicVar(*((TStringId*)&opcodes[index+1]));
stack.push(ctx);
index+=2;
}
continue;
/*
case SET_OTHER_VAR_VAL:
{
const TStringId strId=*((TStringId*)&opcodes[index+1]);
IScriptContext* otherContext = NULL;
if (strId==parentStrId)
{
otherContext = parentContext;
}
else if (strId==callerStrId)
{
otherContext = callerContext;
}
else
{
otherContext = thisContext->findContext(strId);
}
float f = 0.0f;
switch (stack.top().type()) //stack.type(0))
{
case CScriptStack::EString:
{
string& str = stack.top();
f = (float)atof(str.c_str());
}
break;
case CScriptStack::EFloat:
{
f = (float&)stack.top();
}
break;
default:
nlwarning("Stack top type invalid, poping top value!");
}
if (otherContext)
otherContext->setLogicVar(*((TStringId*)&opcodes[index+2]), f);
stack.pop();
index+=3;
}
continue;
case SET_OTHER_STR_VAR_VAL:
{
const TStringId strId=*((TStringId*)&opcodes[index+1]);
IScriptContext* otherContext = NULL;
if (strId==parentStrId)
{
otherContext = parentContext;
}
else if (strId==callerStrId)
{
otherContext = callerContext;
}
else
{
otherContext = thisContext->findContext(strId);
}
std::string str;
switch (stack.top().type())
{
case CScriptStack::EString:
{
str = (string&)stack.top();
}
break;
case CScriptStack::EFloat:
{
float& val = (float&)stack.top();
str = NLMISC::toString("%g", val);
}
break;
default:
nlwarning("Stack top type invalid, poping top value!");
}
if (otherContext)
otherContext->setStrLogicVar(*((TStringId*)&opcodes[index+2]), str);
stack.pop();
index += 3;
}
continue;
case SET_OTHER_CTX_VAR_VAL:
{
TStringId const strId = *((TStringId*)&opcodes[index+1]);
IScriptContext* otherContext = NULL;
if (strId==parentStrId)
{
otherContext = parentContext;
}
else if (strId==callerStrId)
{
otherContext = callerContext;
}
else
{
otherContext = thisContext->findContext(strId);
}
IScriptContext* ctx = (IScriptContext*)0;
switch (stack.top().type())
{
case CScriptStack::EContext:
{
ctx = (IScriptContext*)stack.top();
}
break;
default:
nlwarning("Stack top type invalid, poping top value!");
}
if (otherContext)
otherContext->setCtxLogicVar(*((TStringId*)&opcodes[index+2]), ctx);
stack.pop();
index += 3;
}
continue;
case PUSH_OTHER_VAR_VAL:
{
const TStringId strId=*((TStringId*)&opcodes[index+1]);
IScriptContext* otherContext = NULL;
if (strId==parentStrId)
{
otherContext = parentContext;
}
else if (strId==callerStrId)
{
otherContext = callerContext;
}
else
{
otherContext = thisContext->findContext(strId);
}
float f;
if (otherContext)
f = otherContext->getLogicVar(*((TStringId*)&opcodes[index+2]));
else
f = 1.0f;
stack.push(f);
index += 3;
}
continue;
case PUSH_OTHER_STR_VAR_VAL:
{
const TStringId strId=*((TStringId*)&opcodes[index+1]);
IScriptContext* otherContext = NULL;
if (strId==parentStrId)
{
otherContext = parentContext;
}
else if (strId==callerStrId)
{
otherContext = callerContext;
}
else
{
otherContext = thisContext->findContext(strId);
}
if (otherContext)
stack.push(otherContext->getStrLogicVar(*((TStringId*)&opcodes[index+2])));
else
stack.push(std::string()); // accepted coz const &
index += 3;
}
continue;
case PUSH_OTHER_CTX_VAR_VAL:
{
TStringId const strId = *((TStringId*)&opcodes[index+1]);
IScriptContext* otherContext = NULL;
if (strId==parentStrId)
{
otherContext = parentContext;
}
else if (strId==callerStrId)
{
otherContext = callerContext;
}
else
{
otherContext = thisContext->findContext(strId);
}
if (otherContext)
stack.push(otherContext->getCtxLogicVar(*((TStringId*)&opcodes[index+2])));
else
stack.push((IScriptContext*)0);
index += 3;
}
continue;
*/
case SET_CONTEXT_VAR_VAL:
{
IScriptContext* otherContext = (IScriptContext*)0;
switch (stack.top().type())
{
case CScriptStack::EContext:
{
otherContext = (IScriptContext*)stack.top();
}
break;
default:
nlwarning("Stack top type invalid, poping top value!");
}
stack.pop();
float f = 0.0f;
switch (stack.top().type()) //stack.type(0))
{
case CScriptStack::EString:
{
string& str = stack.top();
f = (float)atof(str.c_str());
}
break;
case CScriptStack::EFloat:
{
f = (float&)stack.top();
}
break;
default:
nlwarning("Stack top type invalid, poping top value!");
}
if (otherContext)
otherContext->setLogicVar(*((TStringId*)&opcodes[index+1]), f);
stack.pop();
index+=2;
}
continue;
case SET_CONTEXT_STR_VAR_VAL:
{
IScriptContext* otherContext = (IScriptContext*)0;
switch (stack.top().type())
{
case CScriptStack::EContext:
{
otherContext = (IScriptContext*)stack.top();
}
break;
default:
nlwarning("Stack top type invalid, poping top value!");
}
stack.pop();
std::string str;
switch (stack.top().type())
{
case CScriptStack::EString:
{
str = (string&)stack.top();
}
break;
case CScriptStack::EFloat:
{
float& val = (float&)stack.top();
str = NLMISC::toString("%g", val);
}
break;
default:
nlwarning("Stack top type invalid, poping top value!");
}
if (otherContext)
otherContext->setStrLogicVar(*((TStringId*)&opcodes[index+1]), str);
stack.pop();
index += 2;
}
continue;
case SET_CONTEXT_CTX_VAR_VAL:
{
IScriptContext* otherContext = (IScriptContext*)0;
switch (stack.top().type())
{
case CScriptStack::EContext:
{
otherContext = (IScriptContext*)stack.top();
}
break;
default:
nlwarning("Stack top type invalid, poping top value!");
}
stack.pop();
IScriptContext* ctx = (IScriptContext*)0;
switch (stack.top().type())
{
case CScriptStack::EContext:
{
ctx = (IScriptContext*)stack.top();
}
break;
default:
nlwarning("Stack top type invalid, poping top value!");
}
if (otherContext)
otherContext->setCtxLogicVar(*((TStringId*)&opcodes[index+1]), ctx);
stack.pop();
index += 2;
}
continue;
case PUSH_CONTEXT_VAR_VAL:
{
IScriptContext* otherContext = (IScriptContext*)0;
switch (stack.top().type())
{
case CScriptStack::EContext:
{
otherContext = (IScriptContext*)stack.top();
}
break;
default:
nlwarning("Stack top type invalid, poping top value!");
}
stack.pop();
float f;
if (otherContext)
f = otherContext->getLogicVar(*((TStringId*)&opcodes[index+1]));
else
f = 1.0f;
stack.push(f);
index += 2;
}
continue;
case PUSH_CONTEXT_STR_VAR_VAL:
{
IScriptContext* otherContext = (IScriptContext*)0;
switch (stack.top().type())
{
case CScriptStack::EContext:
{
otherContext = (IScriptContext*)stack.top();
}
break;
default:
nlwarning("Stack top type invalid, poping top value!");
}
stack.pop();
if (otherContext)
stack.push(otherContext->getStrLogicVar(*((TStringId*)&opcodes[index+1])));
else
stack.push(std::string()); // accepted coz const &
index += 2;
}
continue;
case PUSH_CONTEXT_CTX_VAR_VAL:
{
IScriptContext* otherContext = (IScriptContext*)0;
switch (stack.top().type())
{
case CScriptStack::EContext:
{
otherContext = (IScriptContext*)stack.top();
}
break;
default:
nlwarning("Stack top type invalid, poping top value!");
}
stack.pop();
if (otherContext)
stack.push(otherContext->getCtxLogicVar(*((TStringId*)&opcodes[index+1])));
else
stack.push((IScriptContext*)0);
index += 2;
}
continue;
case JUMP: // Jump + nb size_t to jump (relative). Need: NewJumpOffset After: -
{
index+=opcodes[index+1]+1; // AGI .. Not Opt
}
continue;
case JE: // Jump if last stack value is FALSE(==0). Need: BoolValue(float) (NewJumpOffset on Next Ip) After: -
{
if ((float&)stack.top()==0.f)
index+=opcodes[index+1]+1; // AGI .. Not Opt
else
index+=2;
stack.pop();
}
continue;
case JNE: // Jump if last stack value is TRUE(!=0). Need: BoolValue(float) (NewJumpOffset on Next Ip) After: -
{
if ((float&)stack.top()!=0.f)
index+=opcodes[index+1]+1; // AGI .. Not Opt
else
index+=2;
stack.pop();
}
continue;
case PUSH_PRINT_STRING:
{
currentString+=CStringMapper::unmap(*((TStringId*)&opcodes[index+1])); // strPt.substr(1,strPt.size()-2);
index+=2;
}
continue;
case PUSH_PRINT_VAR:
{
float const val = thisContext->getLogicVar(*((TStringId*)&opcodes[index+1]));
currentString += NLMISC::toString("%g", val);
index += 2;
}
continue;
case PUSH_PRINT_STR_VAR:
{
string const str = thisContext->getStrLogicVar(*((TStringId*)&opcodes[index+1]));
currentString += str;
index += 2;
}
continue;
case PRINT_STRING:
{
if (AIScriptDisplayPrint)
{
nlinfo(currentString.c_str());
}
currentString.resize(0);
++index;
}
continue;
case LOG_STRING:
{
if (AIScriptDisplayLog)
{
nlinfo(currentString.c_str());
}
currentString.resize(0);
++index;
}
continue;
case FUNCTION:
{
// on_event
TStringId const eventName = *((TStringId*)&opcodes[index+1]);
IScriptContext* const sc = stack.top();
stack.pop();
if (sc)
sc->setScriptCallBack(eventName, CByteCodeEntry(byteCode, index+4));
index+=2;
}
continue;
case CALL:
{
// set_event
const TStringId eventName=*((TStringId*)&opcodes[index+1]);
IScriptContext* const sc = stack.top();
stack.pop();
if (sc)
sc->callScriptCallBack(thisContext, eventName, 0, "", "", &stack);
index+=2;
}
continue;
case PUSH_THIS:
{
IScriptContext* const sc=thisContext;
stack.push(sc);
index++;
}
continue;
case PUSH_GROUP:
{
const TStringId strId=*((TStringId*)&opcodes[index+1]);
IScriptContext* otherContext = NULL;
if (strId==parentStrId)
{
otherContext = parentContext;
}
else if (strId==callerStrId)
{
otherContext = callerContext;
}
else
{
otherContext = thisContext->findContext(strId);
}
if (!otherContext)
nlinfo("Group %s unknown, pushing a NULL context on the stack, this may lead to bad behaviours", CStringMapper::unmap(strId).c_str());
stack.push(otherContext);
index+=2;
}
continue;
case PUSH_STRING:
{
const string &str = CStringMapper::unmap(*((TStringId*)&opcodes[index+1]));
stack.push(str);
index+=2;
}
continue;
case ASSIGN_FUNC_FROM:
{
const TStringId srcFunc=CStringMapper::map(stack.top());
stack.pop();
IScriptContext* const src=stack.top();
stack.pop();
const TStringId destFunc=CStringMapper::map(stack.top());
stack.pop();
IScriptContext* const dest=stack.top();
stack.pop();
if ( dest
&& src )
{
CByteCodeEntry const* pcode = src->getScriptCallBackPtr(srcFunc);
if (pcode!=NULL)
dest->setScriptCallBack(destFunc, *pcode);
}
index++;
}
continue;
case NATIVE_CALL:
{
IScriptContext* const sc = stack.top();
stack.pop();
string const& funcName = CStringMapper::unmap(*((TStringId*)&opcodes[++index]));
int mode = (int)opcodes[++index];
string const& inParamsSig = CStringMapper::unmap(*((TStringId*)&opcodes[++index]));
string const& outParamsSig = CStringMapper::unmap(*((TStringId*)&opcodes[++index]));
if (sc)
{
sc->callNativeCallBack(thisContext, funcName, mode, inParamsSig, outParamsSig, &stack);
}
else
{
nlwarning("Calling a native function (%s) on a NULL group/context, rebuilding stack (this situation previously led to unknown behaviour, most of the time crashes)", funcName.c_str());
// If we have parameters we got to rebuild the stack
if (!inParamsSig.empty())
{
for (size_t i=0; iinterpretCodeOnChildren(CByteCodeEntry(byteCode, index+3));
}
index++; // let's go to jump ..
}
continue;
case SWITCH:
{
// !!!!!
size_t compValue=0;
switch (stack.top().type())
{
case CScriptStack::EString:
{
TStringId strId = CStringMapper::map(stack.top());
stack.pop();
compValue = *((size_t*)&strId);
}
break;
case CScriptStack::EFloat:
{
float val = (float&)stack.top();
stack.pop();
compValue = *((size_t*)&val);
}
break;
default:
nlwarning("Stack top type invalid, poping top value");
stack.pop();
}
size_t nbCase=opcodes[index+1];
index+=2; // SWITCH + #Case
stack.push((int)(index+opcodes[index])); // push the absolute address for RET.
index++;
size_t offset=index;
bool found=false;
while (nbCase>0)
{
if (compValue==opcodes[offset]) // we could replace this with binary retrieval.
{
index=offset+1;
index+=opcodes[index]; // we jump at the random sequence.
found=true;
break;
}
nbCase--;
offset+=2;
}
// if not found, don't do anything.
if (!found)
{
index=(int&)stack.top();
stack.pop();
}
}
continue;
case INCR: // Increment top of stack.
{
float &f = stack.top();
++f;
++index;
}
continue;
case DECR: // Decrement top of stack.
{
float &f = stack.top();
--f;
++index;
}
continue;
case CONCAT: // Concatenates 2 strings
{
(string&)stack.top(1) += (string&)stack.top();
stack.pop();
}
continue;
case FTOS: // Convert a float to a string
{
stack.top()=NLMISC::toString("%g", (float&)stack.top());
}
continue;
}
nlassert(false); // must use continue !! Non implementé.
}
}
}