Skip to content

Instantly share code, notes, and snippets.

@jamesu
Created May 6, 2022 17:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jamesu/13186f10fee36c0a387024065fd8b668 to your computer and use it in GitHub Desktop.
Save jamesu/13186f10fee36c0a387024065fd8b668 to your computer and use it in GitHub Desktop.
//
// Copyright (c) 2014 James S Urquhart. All rights reserved.
//
// 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 "ScriptEngine.h"
#include "string/stringTable.h"
#include "sim/simBase.h"
#include "console/consoleNamespace.h"
#include "io/resource/resourceManager.h"
#include "console/consoleInternal.h"
#include "console/consoleDictionary.h"
extern ExprEvalState gEvalState;
// Lua includes
extern "C"
{
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
}
class LuaValueRef : public ScriptEngineValueRef
{
int func;
virtual ScriptEngineValueRef& operator=(const ScriptEngineValueRef &other)
{
static_cast<LuaValueRef&>(other).value = value;
}
};
class LuaScriptStack : public ScriptStack
{
public:
LuaScriptStack()
{
}
virtual ~LuaScriptStack()
{
}
// Push values
virtual int pushNull()
{
lua_pushnil(mLua);
return lua_gettop(mLua);
}
virtual int pushIndex(int value)
{
lua_pushvalue(mLua, value);
return lua_gettop(mLua);
}
virtual int pushInteger(U32 value)
{
lua_pushunsigned(mLua, value);
return lua_gettop(mLua);
}
virtual int pushSignedInteger(S32 value)
{
lua_pushinteger(mLua, value);
return lua_gettop(mLua);
}
virtual int pushNumber(F32 value)
{
lua_pushnumber(mLua, value);
return lua_gettop(mLua);
}
virtual int pushBool(bool value)
{
lua_pushboolean(mLua, value);
return lua_gettop(mLua);
}
virtual int pushSimFields(SimFieldDictionary *values)
{
// basically: make a table
lua_newtable(mLua);
int table = lua_gettop(mLua);
for(SimFieldDictionaryIterator ditr(values); *ditr; ++ditr)
{
SimFieldDictionary::Entry * entry = (*ditr);
lua_pushstring(mLua, entry->slotName);
lua_pushstring(mLua, entry->value);
lua_settable(mLua, table);
}
return lua_gettop(mLua);
}
virtual int pushString(const char *value)
{
lua_pushstring(mLua, value);
return lua_gettop(mLua);
}
virtual int pushSimObject(SimObject *value)
{
// basically: lookup this SimObject in the instance table
lua_pushglobaltable(mLua);
int table = lua_gettop(mLua);
lua_pushstring(mLua, "InstanceTable");
lua_gettable(mLua, table);
// Get Object from InstanceTable
lua_pushinteger(mLua, value->getId());
lua_gettable(mLua, -2);
// Return object
lua_replace(mLua, table);
lua_settop(mLua, table);
return table;
}
// Get values
virtual const char* getString(int index)
{
return lua_tostring(mLua, index);
}
virtual U32 getInteger(int index)
{
return lua_tounsigned(mLua, index);
}
virtual S32 getSignedInteger(int index)
{
return lua_tointeger(mLua, index);
}
virtual F32 getNumber(int index)
{
return lua_tonumber(mLua, index);
}
virtual void getSimFields(int index, SimFieldDictionary *outValues)
{
lua_pushvalue(mLua, index);
int table = lua_gettop(mLua);
lua_pushnil(mLua); /* first key */
while (lua_next(mLua, table) != 0) {
/* uses 'key' (at index -2) and 'value' (at index -1) */
printf("%s - %s\n",
lua_typename(mLua, lua_type(mLua, -2)),
lua_typename(mLua, lua_type(mLua, -1)));
/* removes 'value'; keeps 'key' for next iteration */
outValues->setFieldValue(StringTable->insert(lua_tostring(mLua, -2)), lua_tostring(mLua, -1));
lua_pop(mLua, 1);
}
lua_pop(mLua, 1);
}
virtual bool getBool(int index)
{
return lua_toboolean(mLua, index);
}
virtual ScriptValueBaseType getTypeAtIndex(int index)
{
int tid = lua_type(mLua, index);
switch (tid)
{
case LUA_TNIL:
return SCRIPTTYPE_NULL;
case LUA_TNUMBER:
return SCRIPTTYPE_NUMBER;
case LUA_TBOOLEAN:
return SCRIPTTYPE_BOOL;
case LUA_TSTRING:
return SCRIPTTYPE_STRING;
case LUA_TTABLE:
return SCRIPTTYPE_TABLE;
case LUA_TFUNCTION:
return SCRIPTTYPE_NULL;
case LUA_TUSERDATA:
return SCRIPTTYPE_OBJECT;
default:
return SCRIPTTYPE_NULL;
}
}
// Set values
virtual void setString(int index, const char *value)
{
lua_pushstring(mLua, value);
lua_replace(mLua, index);
}
virtual void setInteger(int index, U32 value)
{
lua_pushunsigned(mLua, value);
lua_replace(mLua, index);
}
virtual void setSignedInteger(int index, S32 value)
{
lua_pushinteger(mLua, value);
lua_replace(mLua, index);
}
virtual void setNumber(int index, F32 value)
{
lua_pushnumber(mLua, value);
lua_replace(mLua, index);
}
virtual void setSimFields(int index, SimFieldDictionary *values)
{
lua_pushvalue(mLua, index);
int table = lua_gettop(mLua);
for(SimFieldDictionaryIterator ditr(values); *ditr; ++ditr)
{
SimFieldDictionary::Entry * entry = (*ditr);
lua_pushstring(mLua, entry->slotName);
lua_pushstring(mLua, entry->value);
lua_settable(mLua, table);
}
lua_pop(mLua, 1);
}
virtual void setSimObject(int index, SimObject *value)
{
// basically: lookup this SimObject in the instance table
lua_pushglobaltable(mLua);
int table = lua_gettop(mLua);
lua_pushstring(mLua, "InstanceTable");
lua_gettable(mLua, -1);
// Get Object from InstanceTable
lua_pushinteger(mLua, value->getId());
lua_gettable(mLua, table+1);
// Replace old index with new object
lua_replace(mLua, index);
lua_settop(mLua, table-1);
}
void setBool(int index, bool value)
{
lua_pushboolean(mLua, value);
lua_replace(mLua, index);
}
public:
lua_State *mLua;
};
class ScriptStackValueRef;
class LuaScriptEngine;
extern ScriptEngine *sScriptInstance;
// taken from lua code
static int typeerror (lua_State *L, int narg, const char *tname) {
const char *msg = lua_pushfstring(L, "%s expected, got %s",
tname, luaL_typename(L, narg));
return luaL_argerror(L, narg, msg);
}
class LuaScriptEngine : public ScriptEngine
{
public:
lua_State *mLuaState;
LuaScriptStack mLuaStack;
bool mRootExec; // flag to determine if an exec call is the root
bool mShouldReset; // flag to determine if next exec call should reset the stack
LuaScriptEngine()
{
mLuaState = luaL_newstate();
mLuaStack.mLua = mLuaState;
sScriptInstance = this;
mRootExec = false;
mShouldReset = false;
luaL_openlibs(mLuaState);
//lua_debug_print_stack(mLuaState);
// Need to setup a few things in the script engine first!
lua_newtable(mLuaState);
lua_setglobal(mLuaState, "InstanceTable");
// Globals table
lua_newtable(mLuaState);
int globalsTable = lua_gettop(mLuaState);
lua_newtable(mLuaState);
int globalsMetaTable = globalsTable+1;
lua_pushstring(mLuaState, "__index");
lua_pushlightuserdata(mLuaState, &(gEvalState.globalVars));
lua_pushcclosure(mLuaState, &getGlobalVariable, 1);
lua_settable(mLuaState, globalsMetaTable);
lua_pushstring(mLuaState, "__newindex");
lua_pushlightuserdata(mLuaState, &(gEvalState.globalVars));
lua_pushcclosure(mLuaState, &setGlobalVariable, 1);
lua_settable(mLuaState, globalsMetaTable);
lua_setmetatable(mLuaState, globalsTable);
lua_setglobal(mLuaState, "TorqueVars");
//lua_debug_print_stack(mLuaState);
}
virtual ~LuaScriptEngine()
{
lua_close(mLuaState);
}
static int getGlobalVariable(lua_State *L)
{
Dictionary *globals = (Dictionary*)lua_touserdata(L, lua_upvalueindex(1));
bool valid = false;
const char *var = globals->getVariable(StringTable->insert(lua_tostring(L, -1)), &valid);
if (valid)
{
lua_pushstring(L, var);
}
else
{
lua_pushnil(L);
}
return 1;
}
static int setGlobalVariable(lua_State *L)
{
Dictionary *globals = (Dictionary*)lua_touserdata(L, lua_upvalueindex(1));
globals->setVariable(StringTable->insert(lua_tostring(L, -2)), lua_tostring(L, -1));
return 0;
}
static void setObjectFieldArray(lua_State *L, int table, StringTableEntry fieldName, SimObject *obj)
{
lua_pushnil(L);
while (lua_next(L, table) != 0) {
/* uses 'key' (at index -2) and 'value' (at index -1) */
printf("%s - %s\n",
lua_typename(L, lua_type(L, -2)),
lua_typename(L, lua_type(L, -1)));
// Use current index
char idxBuf[16];
dSprintf(idxBuf, 16, "%u", dAtoi(lua_tostring(L, -2))+1);
obj->setDataField(fieldName, idxBuf, lua_tostring(L, -1));
lua_pop(L, 1);
}
lua_pop(L, 1);
}
// Constructs an instance of an object in lua
static int makeObject(lua_State *L)
{
StringTableEntry className = StringTable->insert(lua_tostring(L, lua_upvalueindex(1)));
ConsoleObject *conObject = ConsoleObject::create(className);
SimObject *instance = dynamic_cast<SimObject*>(conObject);
SimDataBlock *dataBlock = dynamic_cast<SimDataBlock *>(instance);
SimGroup *grp = NULL;
if (!instance)
{
if (conObject)
delete conObject;
Con::errorf("Could not create object of type %s!", className);
lua_pushnil(L);
return 1;
}
// Second parameter: fields to set
int param = lua_gettop(L);
int startArgs = 2;
if (param > 1)
{
int table = 1;
// Is first parameter a class name?
if (lua_isstring(L, 1))
{
table++;
instance->assignName(lua_tostring(L, 1));
startArgs++;
}
// Check table parameter for initial field values
if (param >= table && lua_istable(L, table))
{
lua_pushnil(L); /* first key */
while (lua_next(L, table) != 0) {
/* uses 'key' (at index -2) and 'value' (at index -1) */
printf("%s - %s\n",
lua_typename(L, lua_type(L, -2)),
lua_typename(L, lua_type(L, -1)));
if (lua_istable(L, -1))
{
// If this field is a table, we need to be a bit more clever...
setObjectFieldArray(L, -1, StringTable->insert(lua_tostring(L, -2)), instance);
}
else
{
// Just set the field the dumb way
instance->setDataField(StringTable->insert(lua_tostring(L, -2)), NULL, lua_tostring(L, -1));
}
lua_pop(L, 1);
}
}
}
// Do the constructor parameters.
const char* fakeArgs[3] = {NULL, NULL, NULL};
if(!instance->processArguments(0, fakeArgs))
{
delete instance;
Con::errorf("Could not create object of type %s!", className);
lua_pushnil(L);
return 1;
}
// If it's not a datablock, allow people to modify bits of it.
if(dataBlock == NULL)
{
instance->setModStaticFields(true);
instance->setModDynamicFields(true);
}
int rest = lua_gettop(L);
if (instance->registerObject())
{
// Handle RootGroup & instantGroup
if(!instance->getGroup())
{
// Deal with the instantGroup if we're being put at the root or we're adding to a component.
const char *addGroupName = Con::getVariable("instantGroup");
if(!Sim::findObject(addGroupName, grp))
Sim::findObject(RootGroupId, grp);
// If we didn't get a group, then make sure we have a pointer to
// the rootgroup.
if(!grp)
Sim::findObject(RootGroupId, grp);
// add to the parent group
grp->addObject(instance);
}
// Are we dealing with a datablock?
static char errorBuffer[256];
// If so, preload it.
if(dataBlock && !dataBlock->preload(true, errorBuffer))
{
Con::errorf(ConsoleLogEntry::General, "%s: preload failed for %s: %s.", "",
instance->getName(), errorBuffer);
dataBlock->deleteObject();
lua_pushnil(L);
return 1;
}
// Grab instance from InstanceTable
lua_pushglobaltable(L);
int table = lua_gettop(L);
lua_pushstring(L, "InstanceTable");
lua_gettable(L, -2);
lua_pushinteger(L, instance->getId());
lua_gettable(L, -2);
return 1;
}
else
{
// This error is usually caused by failing to call Parent::initPersistFields in the class' initPersistFields().
Con::warnf(ConsoleLogEntry::General, "Register object failed for object %s of class %s.", instance->getName(), instance->getClassName());
delete instance;
lua_pushnil(L);
return 1;
}
}
static int gcObject(lua_State *L)
{
// NOTE: we don't garbage collect since the lifetime of the SimObject is controlled by deleteObject.
// TODO: handle case where deleteObject is called but lua still retains a reference to the object.
return 0;
/*
int ud = lua_gettop(L);
const char *className = lua_tostring(L, lua_upvalueindex(1));
SimObject** obj = static_cast<SimObject**>(lua_touserdata(L, ud));
if (obj != NULL)
{
if (lua_getmetatable(L, ud))
{
luaL_getmetatable(L, className);
if (!lua_rawequal(L, -1, -2)) // not the same?
obj = NULL;
lua_pop(L, 2);
}
else
{
obj = NULL;
}
}
if (obj == NULL)
{
typeerror(L, ud, className);
return 0;
}
else
{
(*obj)->unregisterObject();
}*/
return 0;
}
static int getObjectField(lua_State *L)
{
int ud = lua_gettop(L)-1;
const char *className = lua_tostring(L, lua_upvalueindex(1));
#ifdef LUA_USE_DIRECT_SIMOBJECT_PTR
SimObject** objPtr = static_cast<SimObject**>(lua_touserdata(L, ud));
SimObject *obj = objPtr ? *objPtr : NULL;
#else
SimObjectId* objPtr = static_cast<SimObjectId*>(lua_touserdata(L, ud));
SimObject *obj = objPtr ? Sim::findObject(*objPtr) : NULL;
#endif
if (obj != NULL)
{
if (lua_getmetatable(L, ud))
{
luaL_getmetatable(L, className);
if (!lua_rawequal(L, -1, -2)) // not the same?
obj = NULL;
lua_pop(L, 2);
}
else
{
obj = NULL;
}
}
if (obj == NULL)
{
typeerror(L, ud, className);
lua_pushnil(L);
return 1;
}
else
{
// Grab the field name
luaL_getmetatable(L, className);
int meta = lua_gettop(L);
// First, see if klassMetaTable has this...
lua_pushstring(L, "__mthd");
lua_rawget(L, meta); // __mthd
const char *typeOfTop = lua_typename(L, lua_type(L, -1));
lua_pushvalue(L, meta-1);
lua_gettable(L, -2); // fieldName
//lua_debug_print_stack(L);
typeOfTop = lua_typename(L, lua_type(L, -1));
// Grab the field from the object
if (lua_isuserdata(L, -1))
{
StringTableEntry ste = (StringTableEntry)lua_touserdata(L, -1);
const char *data = obj->getDataField(ste, NULL);
if (data)
lua_pushstring(L, data);
else
lua_pushnil(L);
}
else if (lua_isnil(L, -1))
{
// nil? check user fields
StringTableEntry ste = StringTable->insert(lua_tostring(L, meta-1));
const char *data = obj->getDataField(ste, NULL);
if (data)
lua_pushstring(L, data);
else
lua_pushnil(L);
}
}
// Otherwise could be anything, just return it.
return 1;
}
static int setObjectField(lua_State *L)
{
int ud = lua_gettop(L)-2;
int value = lua_gettop(L);
const char *className = lua_tostring(L, lua_upvalueindex(1));
#ifdef LUA_USE_DIRECT_SIMOBJECT_PTR
SimObject** objPtr = static_cast<SimObject**>(lua_touserdata(L, ud));
SimObject *obj = objPtr ? *objPtr : NULL;
#else
SimObjectId* objPtr = static_cast<SimObjectId*>(lua_touserdata(L, ud));
SimObject *obj = objPtr ? Sim::findObject(*objPtr) : NULL;
#endif
if (obj != NULL)
{
if (lua_getmetatable(L, ud))
{
luaL_getmetatable(L, className);
if (!lua_rawequal(L, -1, -2)) // not the same?
obj = NULL;
lua_pop(L, 2);
}
else
{
obj = NULL;
}
}
if (obj == NULL)
{
typeerror(L, ud, className);
return 0;
}
else
{
// Grab the field name
luaL_getmetatable(L, className);
lua_pushvalue(L, -3);
const char *fieldName = lua_tostring(L, -1);
lua_rawget(L, -2);
// Grab the field from the object
if (lua_isuserdata(L, -1))
{
StringTableEntry ste = (StringTableEntry)lua_touserdata(L, -1);
// Determine what type the incoming data is
if (lua_istable(L, value))
{
// Table? Gotta set the array!
setObjectFieldArray(L, value, ste, obj);
}
else
{
// Something else? just set it for now
obj->setDataField(ste, NULL, lua_tostring(L, value));
}
}
else if (lua_isnil(L, -1))
{
StringTableEntry ste = StringTable->insert(fieldName);
// Determine what type the incoming data is
if (lua_istable(L, value))
{
// Table? Gotta set the array!
setObjectFieldArray(L, value, ste, obj);
}
else
{
// Something else? just set it for now
obj->setDataField(ste, NULL, lua_tostring(L, value));
}
}
}
return 0;
}
#define MAX_THUNKARGS 32
static int thunkDummy(lua_State *L)
{
return 0;
}
static inline void* thunkCheckAndReturnObject(lua_State *L, Namespace *requiredNS)
{
#ifdef LUA_USE_DIRECT_SIMOBJECT_PTR
SimObject** objPtr = static_cast<SimObject**>(lua_touserdata(L, 1));
SimObject *obj = objPtr ? *objPtr : NULL;
#else
SimObjectId* objPtr = static_cast<SimObjectId*>(lua_touserdata(L, 1));
SimObject* obj = objPtr ? Sim::findObject(*objPtr) : NULL;
#endif
if (obj != NULL)
{
if (lua_getmetatable(L, 1))
{
lua_pushstring(L, "__ns");
lua_rawget(L, -2);
Namespace *objNS = (Namespace*)lua_touserdata(L, -1);
if (objNS)
{
SimObject *oldObj = obj;
obj = NULL;
for (Namespace *itr = objNS; itr != NULL; itr = itr->mParent)
{
if (itr == requiredNS)
{
// Found the NS, the object is ok!
obj = oldObj;
break;
}
}
}
else
{
obj = NULL;
}
lua_pop(L, 1);
if (obj != NULL)
{
return obj;
}
}
else
{
obj = NULL;
}
}
return NULL;
}
static int thunkString(lua_State *L)
{
int args = lua_gettop(L);
Namespace *ns = (Namespace*)lua_touserdata(L, lua_upvalueindex(5));
SimObject *obj = (SimObject*)thunkCheckAndReturnObject(L, ns);
if (obj == NULL)
{
typeerror(L, 1, ns->mName);
return 0;
}
int minArgs = lua_tointeger(L, lua_upvalueindex(1));
int maxArgs = lua_tointeger(L, lua_upvalueindex(2));
const char *usage = lua_tostring(L, lua_upvalueindex(3));
StringTableEntry fnName = (StringTableEntry)lua_touserdata(L, lua_upvalueindex(4));
if (args+1 < minArgs || args+1 > maxArgs)
{
Con::warnf(ConsoleLogEntry::Script, "%s::%s - wrong number of arguments.", ns->mName, fnName);
Con::warnf(ConsoleLogEntry::Script, "usage: %s", usage);
return 0;
}
// We have enough args, prepare the list
const char *thunkValues[MAX_THUNKARGS];
// Set function name and %this
thunkValues[0] = fnName;
thunkValues[1] = obj->getIdString();
// Populate incomming args
for (int i=0; i<args; i++)
{
thunkValues[i+2] = lua_tostring(L, i+2);
}
StringCallback func = (StringCallback)lua_touserdata(L, lua_upvalueindex(6));
if (func)
{
lua_pushstring(L, func(obj, args+1, thunkValues));
}
else
{
lua_pushstring(L, "");
}
return 1;
}
static int thunkInt(lua_State *L)
{
int args = lua_gettop(L);
Namespace *ns = (Namespace*)lua_touserdata(L, lua_upvalueindex(5));
SimObject *obj = (SimObject*)thunkCheckAndReturnObject(L, ns);
if (obj == NULL)
{
typeerror(L, 1, ns->mName);
return 0;
}
int minArgs = lua_tointeger(L, lua_upvalueindex(1));
int maxArgs = lua_tointeger(L, lua_upvalueindex(2));
const char *usage = lua_tostring(L, lua_upvalueindex(3));
StringTableEntry fnName = (StringTableEntry)lua_touserdata(L, lua_upvalueindex(4));
if (args+1 < minArgs || args+1 > maxArgs)
{
Con::warnf(ConsoleLogEntry::Script, "%s::%s - wrong number of arguments.", ns->mName, fnName);
Con::warnf(ConsoleLogEntry::Script, "usage: %s", usage);
return 0;
}
// We have enough args, prepare the list
const char *thunkValues[MAX_THUNKARGS];
// Set function name and %this
thunkValues[0] = fnName;
thunkValues[1] = obj->getIdString();
// Populate incomming args
for (int i=0; i<args-1; i++)
{
thunkValues[i+2] = lua_tostring(L, i+2);
}
IntCallback func = (IntCallback)lua_touserdata(L, lua_upvalueindex(6));
if (func)
{
lua_pushinteger(L, func(obj, args+1, thunkValues));
}
else
{
lua_pushinteger(L, 0);
}
return 1;
}
static int thunkFloat(lua_State *L)
{
int args = lua_gettop(L);
Namespace *ns = (Namespace*)lua_touserdata(L, lua_upvalueindex(5));
SimObject *obj = (SimObject*)thunkCheckAndReturnObject(L, ns);
if (obj == NULL)
{
typeerror(L, 1, ns->mName);
return 0;
}
int minArgs = lua_tointeger(L, lua_upvalueindex(1));
int maxArgs = lua_tointeger(L, lua_upvalueindex(2));
const char *usage = lua_tostring(L, lua_upvalueindex(3));
StringTableEntry fnName = (StringTableEntry)lua_touserdata(L, lua_upvalueindex(4));
if (args+1 < minArgs || args+1 > maxArgs)
{
Con::warnf(ConsoleLogEntry::Script, "%s::%s - wrong number of arguments.", ns->mName, fnName);
Con::warnf(ConsoleLogEntry::Script, "usage: %s", usage);
return 0;
}
// We have enough args, prepare the list
const char *thunkValues[MAX_THUNKARGS];
// Set function name and %this
thunkValues[0] = fnName;
thunkValues[1] = obj->getIdString();
// Populate incomming args
for (int i=0; i<args; i++)
{
thunkValues[i+2] = lua_tostring(L, i+2);
}
FloatCallback func = (FloatCallback)lua_touserdata(L, lua_upvalueindex(6));
if (func)
{
lua_pushnumber(L, func(obj, args+1, thunkValues));
}
else
{
lua_pushnumber(L, 0);
}
return 1;
}
static int thunkBool(lua_State *L)
{
int args = lua_gettop(L);
Namespace *ns = (Namespace*)lua_touserdata(L, lua_upvalueindex(5));
SimObject *obj = (SimObject*)thunkCheckAndReturnObject(L, ns);
if (obj == NULL)
{
typeerror(L, 1, ns->mName);
return 0;
}
int minArgs = lua_tointeger(L, lua_upvalueindex(1));
int maxArgs = lua_tointeger(L, lua_upvalueindex(2));
const char *usage = lua_tostring(L, lua_upvalueindex(3));
StringTableEntry fnName = (StringTableEntry)lua_touserdata(L, lua_upvalueindex(4));
if (args+1 < minArgs || args+1 > maxArgs)
{
Con::warnf(ConsoleLogEntry::Script, "%s::%s - wrong number of arguments.", ns->mName, fnName);
Con::warnf(ConsoleLogEntry::Script, "usage: %s", usage);
return 0;
}
// We have enough args, prepare the list
const char *thunkValues[MAX_THUNKARGS];
// Set function name and %this
thunkValues[0] = fnName;
thunkValues[1] = obj->getIdString();
// Populate incomming args
for (int i=0; i<args; i++)
{
thunkValues[i+2] = lua_tostring(L, i+2);
}
BoolCallback func = (BoolCallback)lua_touserdata(L, lua_upvalueindex(6));
if (func)
{
lua_pushboolean(L, func(obj, args+1, thunkValues));
}
else
{
lua_pushboolean(L, false);
}
return 1;
}
static int thunkVoid(lua_State *L)
{
int args = lua_gettop(L);
Namespace *ns = (Namespace*)lua_touserdata(L, lua_upvalueindex(5));
SimObject *obj = (SimObject*)thunkCheckAndReturnObject(L, ns);
if (obj == NULL)
{
typeerror(L, 1, ns->mName);
return 0;
}
int minArgs = lua_tointeger(L, lua_upvalueindex(1));
int maxArgs = lua_tointeger(L, lua_upvalueindex(2));
const char *usage = lua_tostring(L, lua_upvalueindex(3));
StringTableEntry fnName = (StringTableEntry)lua_touserdata(L, lua_upvalueindex(4));
if (args+1 < minArgs || args+1 > maxArgs)
{
Con::warnf(ConsoleLogEntry::Script, "%s::%s - wrong number of arguments.", ns->mName, fnName);
Con::warnf(ConsoleLogEntry::Script, "usage: %s", usage);
return 0;
}
// We have enough args, prepare the list
const char *thunkValues[MAX_THUNKARGS];
// Set function name and %this
thunkValues[0] = fnName;
thunkValues[1] = obj->getIdString();
// Populate incomming args
for (int i=0; i<args; i++)
{
thunkValues[i+2] = lua_tostring(L, i+2);
}
VoidCallback func = (VoidCallback)lua_touserdata(L, lua_upvalueindex(6));
if (func)
func(obj, args+1, thunkValues);
return 0;
}
// Global thunks
static int thunkGlobalString(lua_State *L)
{
int args = lua_gettop(L);
Namespace *ns = (Namespace*)lua_touserdata(L, lua_upvalueindex(5));
int minArgs = lua_tointeger(L, lua_upvalueindex(1));
int maxArgs = lua_tointeger(L, lua_upvalueindex(2));
const char *usage = lua_tostring(L, lua_upvalueindex(3));
StringTableEntry fnName = (StringTableEntry)lua_touserdata(L, lua_upvalueindex(4));
if (args+1 < minArgs || (maxArgs != 0 && (args+1 > maxArgs)))
{
Con::warnf(ConsoleLogEntry::Script, "%s::%s - wrong number of arguments.", ns->mName, fnName);
Con::warnf(ConsoleLogEntry::Script, "usage: %s", usage);
return 0;
}
// We have enough args, prepare the list
const char *thunkValues[MAX_THUNKARGS];
// Set function name and %this
thunkValues[0] = fnName;
// Populate incomming args
for (int i=0; i<args; i++)
{
thunkValues[i+1] = lua_tostring(L, i+1);
}
StringCallback func = (StringCallback)lua_touserdata(L, lua_upvalueindex(6));
if (func)
{
lua_pushstring(L, func(NULL, args+1, thunkValues));
}
else
{
lua_pushstring(L, "");
}
return 1;
}
static int thunkGlobalInt(lua_State *L)
{
int args = lua_gettop(L);
Namespace *ns = (Namespace*)lua_touserdata(L, lua_upvalueindex(5));
int minArgs = lua_tointeger(L, lua_upvalueindex(1));
int maxArgs = lua_tointeger(L, lua_upvalueindex(2));
const char *usage = lua_tostring(L, lua_upvalueindex(3));
StringTableEntry fnName = (StringTableEntry)lua_touserdata(L, lua_upvalueindex(4));
if (args+1 < minArgs || (maxArgs != 0 && (args+1 > maxArgs)))
{
Con::warnf(ConsoleLogEntry::Script, "%s::%s - wrong number of arguments.", ns->mName, fnName);
Con::warnf(ConsoleLogEntry::Script, "usage: %s", usage);
return 0;
}
// We have enough args, prepare the list
const char *thunkValues[MAX_THUNKARGS];
// Set function name and %this
thunkValues[0] = fnName;
// Populate incomming args
for (int i=0; i<args; i++)
{
thunkValues[i+1] = lua_tostring(L, i+1);
}
IntCallback func = (IntCallback)lua_touserdata(L, lua_upvalueindex(6));
if (func)
{
lua_pushinteger(L, func(NULL, args+1, thunkValues));
}
else
{
lua_pushinteger(L, 0);
}
return 1;
}
static int thunkGlobalFloat(lua_State *L)
{
int args = lua_gettop(L);
Namespace *ns = (Namespace*)lua_touserdata(L, lua_upvalueindex(5));
int minArgs = lua_tointeger(L, lua_upvalueindex(1));
int maxArgs = lua_tointeger(L, lua_upvalueindex(2));
const char *usage = lua_tostring(L, lua_upvalueindex(3));
StringTableEntry fnName = (StringTableEntry)lua_touserdata(L, lua_upvalueindex(4));
if (args+1 < minArgs || (maxArgs != 0 && (args+1 > maxArgs)))
{
Con::warnf(ConsoleLogEntry::Script, "%s::%s - wrong number of arguments.", ns->mName, fnName);
Con::warnf(ConsoleLogEntry::Script, "usage: %s", usage);
return 0;
}
// We have enough args, prepare the list
const char *thunkValues[MAX_THUNKARGS];
// Set function name and %this
thunkValues[0] = fnName;
// Populate incomming args
for (int i=0; i<args; i++)
{
thunkValues[i+1] = lua_tostring(L, i+1);
}
FloatCallback func = (FloatCallback)lua_touserdata(L, lua_upvalueindex(6));
if (func)
{
lua_pushnumber(L, func(NULL, args+1, thunkValues));
}
else
{
lua_pushnumber(L, 0);
}
return 1;
}
static int thunkGlobalBool(lua_State *L)
{
int args = lua_gettop(L);
Namespace *ns = (Namespace*)lua_touserdata(L, lua_upvalueindex(5));
int minArgs = lua_tointeger(L, lua_upvalueindex(1));
int maxArgs = lua_tointeger(L, lua_upvalueindex(2));
const char *usage = lua_tostring(L, lua_upvalueindex(3));
StringTableEntry fnName = (StringTableEntry)lua_touserdata(L, lua_upvalueindex(4));
if (args+1 < minArgs || (maxArgs != 0 && (args+1 > maxArgs)))
{
Con::warnf(ConsoleLogEntry::Script, "%s::%s - wrong number of arguments.", ns->mName, fnName);
Con::warnf(ConsoleLogEntry::Script, "usage: %s", usage);
return 0;
}
// We have enough args, prepare the list
const char *thunkValues[MAX_THUNKARGS];
// Set function name and %this
thunkValues[0] = fnName;
// Populate incomming args
for (int i=0; i<args; i++)
{
thunkValues[i+1] = lua_tostring(L, i+1);
}
BoolCallback func = (BoolCallback)lua_touserdata(L, lua_upvalueindex(6));
if (func)
{
lua_pushboolean(L, func(NULL, args+1, thunkValues));
}
else
{
lua_pushboolean(L, false);
}
return 1;
}
static int thunkGlobalVoid(lua_State *L)
{
int args = lua_gettop(L);
Namespace *ns = (Namespace*)lua_touserdata(L, lua_upvalueindex(5));
int minArgs = lua_tointeger(L, lua_upvalueindex(1));
int maxArgs = lua_tointeger(L, lua_upvalueindex(2));
const char *usage = lua_tostring(L, lua_upvalueindex(3));
StringTableEntry fnName = (StringTableEntry)lua_touserdata(L, lua_upvalueindex(4));
if (args+1 < minArgs || (maxArgs != 0 && (args+1 > maxArgs)))
{
Con::warnf(ConsoleLogEntry::Script, "%s::%s - wrong number of arguments.", ns->mName, fnName);
Con::warnf(ConsoleLogEntry::Script, "usage: %s", usage);
return 0;
}
// We have enough args, prepare the list
const char *thunkValues[MAX_THUNKARGS];
// Set function name and %this
thunkValues[0] = fnName;
// Populate incomming args
for (int i=0; i<args; i++)
{
thunkValues[i+1] = lua_tostring(L, i+1);
}
VoidCallback func = (VoidCallback)lua_touserdata(L, lua_upvalueindex(6));
if (func)
func(NULL, args+1, thunkValues);
return 0;
}
void registerClassWithNS(AbstractClassRep *rep, Namespace *ns)
{
// Make master class table
lua_newtable(mLuaState);
int klassTable = lua_gettop(mLuaState);
lua_pushvalue(mLuaState, klassTable);
lua_setglobal(mLuaState, ns->mName);
lua_newtable(mLuaState);
int klassMetaTable = lua_gettop(mLuaState);
// Define constructor function
lua_pushstring(mLuaState, "__call");
lua_pushstring(mLuaState, ns->mName);
lua_pushcclosure(mLuaState, &makeObject, 1);
lua_settable(mLuaState, klassMetaTable);
int newTop = lua_gettop(mLuaState);
// Register userdata metatable in the lua registry
luaL_newmetatable(mLuaState, ns->mName);
int metatable = lua_gettop(mLuaState);
// Assign field StringTableEntry map for property get/set
int fieldIndex = 0;
for (Vector<AbstractClassRep::Field>::const_iterator itr = rep->mFieldList.begin(); itr != rep->mFieldList.end(); itr++)
{
if (itr->type == AbstractClassRep::StartGroupFieldType || itr->type == AbstractClassRep::EndGroupFieldType) {
fieldIndex++;
continue;
}
lua_pushlightuserdata(mLuaState, (void*)StringTable->insert(itr->pFieldname));
lua_pushstring(mLuaState, itr->pFieldname);
lua_settable(mLuaState, metatable);
fieldIndex++;
}
// Assign function pointers for functions on native class
Vector<Namespace::Entry*> entryList;
Namespace *klassNS = rep->getNameSpace();
klassNS->getLocalEntryList(&entryList);
for (Vector<Namespace::Entry*>::const_iterator itr = entryList.begin(); itr != entryList.end(); itr++)
{
const Namespace::Entry *ent = *itr;
// Skip doc entries
if (ent->mType < Namespace::Entry::ScriptFunctionType)
continue;
// function name
lua_pushstring(mLuaState, ent->mFunctionName);
int funcName = lua_gettop(mLuaState);
// min, max args, usage, STE name
lua_pushinteger(mLuaState, ent->mMinArgs);
lua_pushinteger(mLuaState, ent->mMaxArgs);
lua_pushstring(mLuaState, ent->mUsage);
lua_pushlightuserdata(mLuaState, (void*)ent->mFunctionName);
lua_pushlightuserdata(mLuaState, ns);
// function pointer
lua_pushlightuserdata(mLuaState, (void*)ent->cb.mStringCallbackFunc);
switch (ent->mType)
{
case Namespace::Entry::ScriptFunctionType:
lua_pushcclosure(mLuaState, &thunkDummy, 6);
break;
case Namespace::Entry::StringCallbackType:
lua_pushcclosure(mLuaState, &thunkString, 6);
break;
case Namespace::Entry::IntCallbackType:
lua_pushcclosure(mLuaState, &thunkInt, 6);
break;
case Namespace::Entry::FloatCallbackType:
lua_pushcclosure(mLuaState, &thunkFloat, 6);
break;
case Namespace::Entry::BoolCallbackType:
lua_pushcclosure(mLuaState, &thunkBool, 6);
break;
case Namespace::Entry::VoidCallbackType:
default:
lua_pushcclosure(mLuaState, &thunkVoid, 6);
break;
}
int topNow = lua_gettop(mLuaState);
// Set entry
lua_settable(mLuaState, klassTable);
}
// garbage collection
lua_pushstring(mLuaState, "__gc");
lua_pushstring(mLuaState, ns->mName);
lua_pushcclosure(mLuaState, &gcObject, 1);
lua_settable(mLuaState, metatable);
// property setter
lua_pushstring(mLuaState, "__newindex");
lua_pushstring(mLuaState, ns->mName);
lua_pushcclosure(mLuaState, &setObjectField, 1);
lua_settable(mLuaState, metatable);
lua_pushstring(mLuaState, "__ns");
lua_pushlightuserdata(mLuaState, ns);
lua_settable(mLuaState, metatable);
// Set class metatable
lua_pushvalue(mLuaState, klassMetaTable);
lua_setmetatable(mLuaState, klassTable);
// parent namespace
Namespace *rootNS = rep->getNameSpace();
if (rootNS != ns)
{
Namespace *itrNS = ns;
for (itrNS = ns; itrNS != rootNS; itrNS = itrNS->mParent)
{
// Basically what we need to do is as follows:
// - Ensure the table exists for any namespaces
// - Link the previous namespace to the table
lua_getglobal(mLuaState, itrNS->mName);
// create table if not present
if (lua_isnil(mLuaState, -1))
{
lua_pop(mLuaState, 1);
lua_newtable(mLuaState);
lua_pushvalue(mLuaState, -1);
lua_pushvalue(mLuaState, -1);
lua_setglobal(mLuaState, ns->mName);
}
// create metatable if not present
lua_getmetatable(mLuaState, -1);
if (lua_isnil(mLuaState, -1))
{
lua_pop(mLuaState, 1);
lua_newtable(mLuaState);
lua_pushvalue(mLuaState, -1);
lua_pushvalue(mLuaState, -1);
lua_setmetatable(mLuaState, -3);
}
// create parent table if it doesn't exist (metatable be set on the next iteration)
lua_getglobal(mLuaState, ns->mParent->mName);
if (lua_isnil(mLuaState, -1))
{
lua_pop(mLuaState, 1);
lua_newtable(mLuaState);
lua_pushvalue(mLuaState, -1);
lua_pushvalue(mLuaState, -1);
lua_setglobal(mLuaState, ns->mParent->mName);
}
// now we have: [parent] [namespace metatable]
// finally set the metatable __index to the parent table
lua_pushstring(mLuaState, "__index");
lua_settable(mLuaState, -2);
}
// We should now be linked up!
}
else if (rep->getParentClass()) // parent class (only applicable for inbuilt classes)
{
lua_pushstring(mLuaState, "__index");
lua_getglobal(mLuaState, rep->getParentClass()->getClassName());
if (!lua_isnil(mLuaState, -1))
{
// defer to the users parent class table
lua_settable(mLuaState, klassMetaTable); // klassMetaTable.__index == $G[rep->getParentClass()->getClassName()]
}
else
{
lua_pop(mLuaState, 1);
}
}
// set __mthd fallback table
lua_pushstring(mLuaState, "__mthd");
lua_pushvalue(mLuaState, klassTable);
lua_settable(mLuaState, metatable);
// Root class table property getter goes through c function
lua_pushstring(mLuaState, "__index");
lua_pushstring(mLuaState, ns->mName);
lua_pushcclosure(mLuaState, &getObjectField, 1);
lua_settable(mLuaState, metatable);
lua_settop(mLuaState, klassTable-1);
}
// register class
virtual void registerClass(AbstractClassRep *rep)
{
registerClassWithNS(rep, rep->getNameSpace());
}
int linkLuaNamespaces(AbstractClassRep *rep, Namespace *ns)
{
Namespace *rootNS = rep->getNameSpace();
// TODO: since linkNamespaces is called in onAdd, this won't work without refactoring!
//if (rootNS == NULL) {
// This will usually happen when Sim is not init'd yet. In this case lets just use the class name
luaL_getmetatable(mLuaState, rep->getClassName());
return lua_gettop(mLuaState);
//}
/*
// First check the desired class name. If it's different, we'll likely have to register a new class
if (ns != rootNS)
{
// Firstly, is the name already registered? If so we can skip this
lua_pushstring(mLuaState, ns->mName);
lua_getmetatable(mLuaState, -1);
if (!lua_isnil(mLuaState, -1))
{
return lua_gettop(mLuaState);
}
// Register this new class
registerClassWithNS(rep, rep->getNameSpace());
}
// Grab the global class metatable
luaL_getmetatable(mLuaState, ns->mName);
return lua_gettop(mLuaState);*/
}
// register object instance
virtual void registerObject(SimObject *object)
{
int top = lua_gettop(mLuaState);
Namespace *ns = object->getNamespace();
int objNS = linkLuaNamespaces(object->getClassRep(), ns);
int klassMetatable = -1;
//
// Basically make a userdata object bound to our class name
#ifdef LUA_USE_DIRECT_SIMOBJECT_PTR
SimObject** ptr = (SimObject**)lua_newuserdata(mLuaState, sizeof(SimObject*));
*ptr = object;
#else
SimObjectId* ptr = (SimObjectId*)lua_newuserdata(mLuaState, sizeof(SimObjectId));
*ptr = object->getId();
#endif
int userdata = lua_gettop(mLuaState);
lua_pushvalue(mLuaState, objNS);
lua_setmetatable(mLuaState, userdata);
// Now store this in InstanceTable
lua_pushglobaltable(mLuaState);
lua_pushstring(mLuaState, "InstanceTable");
lua_gettable(mLuaState, -2);
lua_pushinteger(mLuaState, object->getId());
lua_pushvalue(mLuaState, userdata);
lua_settable(mLuaState, -3);
// Restore stack
lua_settop(mLuaState, top);
}
virtual void registerFunctions(Namespace *ns)
{
// Add all functions from namespace
Vector<Namespace::Entry*> entryList;
ns->getLocalEntryList(&entryList);
lua_newtable(mLuaState);
int nsTable = lua_gettop(mLuaState);
lua_pushglobaltable(mLuaState);
// Globals go in Torque, otherwise we use the namespace name
if (Namespace::global() == ns)
{
lua_pushstring(mLuaState, "Torque");
lua_pushvalue(mLuaState, 1);
lua_rawset(mLuaState, -3);
}
else
{
lua_pushstring(mLuaState, ns->mName);
lua_pushvalue(mLuaState, 1);
lua_rawset(mLuaState, -3);
}
lua_pop(mLuaState, 1);
for (Vector<Namespace::Entry*>::const_iterator itr = entryList.begin(); itr != entryList.end(); itr++)
{
const Namespace::Entry *ent = *itr;
// Skip doc entries
if (ent->mType < Namespace::Entry::ScriptFunctionType)
continue;
// function name
lua_pushstring(mLuaState, ent->mFunctionName);
int funcName = lua_gettop(mLuaState);
// min, max args, usage, STE name
lua_pushinteger(mLuaState, ent->mMinArgs);
lua_pushinteger(mLuaState, ent->mMaxArgs);
lua_pushstring(mLuaState, ent->mUsage);
lua_pushlightuserdata(mLuaState, (void*)ent->mFunctionName);
lua_pushlightuserdata(mLuaState, ns);
// function pointer
lua_pushlightuserdata(mLuaState, (void*)ent->cb.mStringCallbackFunc);
switch (ent->mType)
{
case Namespace::Entry::ScriptFunctionType:
lua_pushcclosure(mLuaState, &thunkDummy, 6);
break;
case Namespace::Entry::StringCallbackType:
lua_pushcclosure(mLuaState, &thunkGlobalString, 6);
break;
case Namespace::Entry::IntCallbackType:
lua_pushcclosure(mLuaState, &thunkGlobalInt, 6);
break;
case Namespace::Entry::FloatCallbackType:
lua_pushcclosure(mLuaState, &thunkGlobalFloat, 6);
break;
case Namespace::Entry::BoolCallbackType:
lua_pushcclosure(mLuaState, &thunkGlobalBool, 6);
break;
case Namespace::Entry::VoidCallbackType:
default:
lua_pushcclosure(mLuaState, &thunkGlobalVoid, 6);
break;
}
// Set entry
lua_settable(mLuaState, nsTable);
}
lua_pop(mLuaState, 1);
}
virtual void removeObject(SimObject *object)
{
// Remove object from InstanceTable
lua_pushglobaltable(mLuaState);
int table = lua_gettop(mLuaState);
lua_pushstring(mLuaState, "InstanceTable");
lua_rawget(mLuaState, -2);
if (!lua_isnil(mLuaState, -1))
{
lua_pushinteger(mLuaState, object->getId());
lua_pushnil(mLuaState);
lua_settable(mLuaState, -3);
}
lua_settop(mLuaState, table-1);
}
// gets current stack
virtual ScriptStack *getStack()
{
return &mLuaStack;
}
virtual ScriptStackValueRef execute(S32 argc, ScriptStackValueRef argv[])
{
ScriptStackValueRef ret;
ret.type = SCRIPTTYPE_NULL;
ret.index = -1;
int start = lua_gettop(mLuaState) - argc;
bool wasRootExec = false;
if (!mRootExec)
{
wasRootExec = true;
mRootExec = true;
}
// Resolve function
lua_pushglobaltable(mLuaState);
lua_pushvalue(mLuaState, argv[0].index);
const char *funcName = lua_tostring(mLuaState, argv[0].index);
lua_gettable(mLuaState, -2);
lua_replace(mLuaState, argv[0].index);
lua_pop(mLuaState, 1);
lua_debug_print_stack(mLuaState);
if (lua_pcall(mLuaState, argc-1, 1, 0) != 0)
{
Con::errorf("Error calling %s (%s)", funcName, lua_tostring(mLuaState, -1));
lua_settop(mLuaState, 0);
lua_pushnil(mLuaState);
ret.type = SCRIPTTYPE_NULL;
ret.index = 1;
if (wasRootExec)
{
mShouldReset = true;
}
return ret;
}
if (wasRootExec)
{
mShouldReset = true;
}
// Return result
ret.index = lua_gettop(mLuaState);
ret.type = mLuaStack.getTypeAtIndex(-1);
return ret;
}
// print stack. useful if you get confused.
static void lua_debug_print_stack(lua_State *L)
{
int newtop = lua_gettop(L);
printf("--------\n");
for (int i=1; i<newtop+1; i++)
{
printf("Stack value[%i]: %s [%s]\n", i, lua_tostring(L, i), lua_typename(L, lua_type(L, i)));
}
printf("--------\n");
}
virtual ScriptStackValueRef executeOnObject(ScriptStackValueRef obj, S32 argc, ScriptStackValueRef argv[])
{
ScriptStackValueRef ret;
ret.type = SCRIPTTYPE_NULL;
ret.index = -1;
bool wasRootExec = false;
if (!mRootExec)
{
wasRootExec = true;
mRootExec = true;
}
if (mShouldReset)
{
lua_settop(mLuaState, 0);
}
int start = lua_gettop(mLuaState) - argc; // i.e. first parameter
// Make sure obj is second parameter
if (obj.index < start)
{
// Move obj to start
lua_pushvalue(mLuaState, obj.index);
lua_insert(mLuaState, start+1);
lua_remove(mLuaState, obj.index);
obj.index = start;
argv[0].index -= 1;
}
else if (obj.index == lua_gettop(mLuaState))
{
lua_insert(mLuaState, start+1);
obj.index = start+1;
}
// Resolve object field, replace arg 0
lua_pushvalue(mLuaState, argv[0].index);
const char *funcName = lua_tostring(mLuaState, -1);
lua_gettable(mLuaState, obj.index);
lua_replace(mLuaState, argv[0].index);
//lua_debug_print_stack(mLuaState);
if (lua_pcall(mLuaState, argc, 1, 0) != 0)
{
Con::errorf("Error calling %s (%s)", funcName, lua_tostring(mLuaState, -1));
lua_settop(mLuaState, 0);
lua_pushnil(mLuaState);
ret.type = SCRIPTTYPE_NULL;
ret.index = 1;
if (wasRootExec)
{
mShouldReset = true;
}
return ret;
}
if (wasRootExec)
{
mShouldReset = true;
}
// Return result
ret.index = lua_gettop(mLuaState);
ret.type = mLuaStack.getTypeAtIndex(-1);
return ret;
}
/// Evaluate an arbitrary chunk of code.
///
/// @param string Buffer containing code to execute.
/// @param echo Should we echo the string to the console?
/// @param fileName Indicate what file this code is coming from; used in error reporting and such.
virtual ScriptStackValueRef evaluate(const char* string, bool echo = false, const char *fileName = NULL)
{
ScriptStackValueRef ret;
ret.type = SCRIPTTYPE_NULL;
ret.index = -1;
int start = lua_gettop(mLuaState);
bool wasRootExec = false;
if (!mRootExec)
{
wasRootExec = true;
mRootExec = true;
}
if (mShouldReset)
{
lua_settop(mLuaState, 0);
}
// Load the script
if (luaL_loadbuffer(mLuaState, string, dStrlen(string), fileName) != 0)
{
Con::errorf("Error parsing script %s: %s\n", fileName ? fileName : "", lua_tostring(mLuaState, -1));
lua_pop(mLuaState, 1);
lua_pushnil(mLuaState);
ret.type = SCRIPTTYPE_NULL;
ret.index = lua_gettop(mLuaState);
return ret;
}
//lua_debug_print_stack(mLuaState);
if (lua_pcall(mLuaState, 0, 1, 0) != 0)
{
Con::errorf("Error running script: %s\n", lua_tostring(mLuaState, -1));
lua_pop(mLuaState, 1);
lua_pushnil(mLuaState);
ret.type = SCRIPTTYPE_NULL;
ret.index = lua_gettop(mLuaState);
return ret;
}
if (wasRootExec)
{
mShouldReset = true;
}
// Return result
ret.index = lua_gettop(mLuaState);
ret.type = mLuaStack.getTypeAtIndex(-1);
return ret;
}
static int doLuaWrite(lua_State *L,
const void* p,
size_t sz,
void* ud)
{
Stream *s = static_cast<Stream*>(ud);
s->write(sz, p);
return 0;
}
virtual bool compileFile(const char *filename)
{
char pathBuffer[1024];
Con::expandPath(pathBuffer, sizeof(pathBuffer), filename);
// Load the script
Stream *s = ResourceManager->openStream(pathBuffer);
if (!s)
{
Con::errorf(ConsoleLogEntry::Script, "Could not load script %s", pathBuffer);
return false;
}
U32 size = s->getStreamSize();
char *buffer = (char*)dMalloc(size+1);
s->read(size, buffer);
ResourceManager->closeStream(s);
if (luaL_loadbuffer(mLuaState, buffer, size, pathBuffer) != 0)
{
Con::errorf(ConsoleLogEntry::Script, "Error parsing script %s: %s\n", pathBuffer, lua_tostring(mLuaState, -1));
lua_pop(mLuaState, 1);
return false;
}
dFree(buffer);
// Add extension and dump to bytecode file
char *ext = dStrrchr( pathBuffer, '.' );
if (!ext) {
Con::errorf(ConsoleLogEntry::Script, "No extension for script %s", pathBuffer);
return false;
}
dStrcpy(ext, ".luc");
FileStream outS;
if (!ResourceManager->openFileForWrite(outS, pathBuffer))
{
Con::errorf(ConsoleLogEntry::Script, "Error opening %s for write", pathBuffer);
lua_pop(mLuaState, 1);
return false;
}
lua_dump(mLuaState, &doLuaWrite, &outS);
lua_pop(mLuaState, 1);
return true;
}
virtual bool executeFile(const char *filename)
{
char pathBuffer[1024];
Con::expandPath(pathBuffer, sizeof(pathBuffer), filename);
FileTime comModifyTime;
FileTime scrModifyTime;
ResourceObject *rScr = ResourceManager->find(pathBuffer);
ResourceObject *rCom = NULL;
// Check if .luc exists
char *ext = dStrrchr( pathBuffer, '.' );
if (!ext) {
Con::errorf(ConsoleLogEntry::Script, "No extension for script %s", pathBuffer);
return false;
}
dStrcpy(ext, ".luc");
rCom = ResourceManager->find(pathBuffer);
if(rCom)
rCom->getFileTimes(NULL, &comModifyTime);
if(rScr)
rScr->getFileTimes(NULL, &scrModifyTime);
// If com exists and its newer than the script, just load the compiled version.
if (rCom && ( rScr == NULL || (rScr && Platform::compareFileTimes(comModifyTime, scrModifyTime) >= 0)) )
{
// Load the script
Stream *s = ResourceManager->openStream(rCom);
if (!s)
{
Con::errorf(ConsoleLogEntry::Script, "Could not load script %s", pathBuffer);
return false;
}
U32 size = s->getStreamSize();
char *buffer = (char*)dMalloc(size+1);
s->read(size, buffer);
ResourceManager->closeStream(s);
// re-eval original filename
Con::expandPath(pathBuffer, sizeof(pathBuffer), filename);
if (luaL_loadbuffer(mLuaState, buffer, size, pathBuffer) != 0)
{
Con::errorf(ConsoleLogEntry::Script, "Error loading script %s: %s\n", pathBuffer, lua_tostring(mLuaState, -1));
lua_pop(mLuaState, 1);
return false;
}
dFree(buffer);
if (lua_pcall(mLuaState, 0, 0, 0) != 0)
{
Con::errorf(ConsoleLogEntry::Script, "Error running script: %s\n", lua_tostring(mLuaState, -1));
lua_pop(mLuaState, 1);
return false;
}
lua_pop(mLuaState, 1);
return true;
}
// Otherwise, compile script
if (rScr)
{
// Load the script
Stream *s = ResourceManager->openStream(rScr);
if (!s)
{
Con::errorf(ConsoleLogEntry::Script, "Could not load script %s", pathBuffer);
return false;
}
U32 size = s->getStreamSize();
char *buffer = (char*)dMalloc(size+1);
s->read(size, buffer);
ResourceManager->closeStream(s);
// Open compiled version file
FileStream outF;
Stream *outS = NULL;
if (ResourceManager->openFileForWrite(outF, pathBuffer))
{
outS = &outF;
}
// re-eval original filename
Con::expandPath(pathBuffer, sizeof(pathBuffer), filename);
if (luaL_loadbuffer(mLuaState, buffer, size, pathBuffer) != 0)
{
Con::errorf(ConsoleLogEntry::Script, "Error parsing script %s: %s\n", pathBuffer, lua_tostring(mLuaState, -1));
lua_pop(mLuaState, 1);
return false;
}
dFree(buffer);
// dump to compiled bytecode file
if (outS)
{
lua_dump(mLuaState, &doLuaWrite, outS);
outF.close();
}
if (lua_pcall(mLuaState, 0, 0, 0) != 0)
{
Con::errorf(ConsoleLogEntry::Script, "Error running script: %s\n", lua_tostring(mLuaState, -1));
lua_pop(mLuaState, 1);
return false;
}
lua_pop(mLuaState, 1);
return true;
}
Con::expandPath(pathBuffer, sizeof(pathBuffer), filename);
Con::errorf(ConsoleLogEntry::Script, "Could not execute script %s\n", pathBuffer);
return false;
}
};
extern ScriptEngine *sScriptInstance;
void SetupLuaInterpreter()
{
ScriptEngine *script = ScriptEngine::getInstance();
if (!script)
{
script = new LuaScriptEngine();
script->registerFunctions(Namespace::global());
AbstractClassRep *klass = AbstractClassRep::findClassRep("SimObject");
script->registerClass(klass);
klass = AbstractClassRep::findClassRep("AssetBase");
script->registerClass(klass);
}
}
void ShutdownLuaInterpreter()
{
if (sScriptInstance)
delete sScriptInstance;
sScriptInstance = NULL;
}
ConsoleFunction(evalLua, const char*, 2, 2, "")
{
return ScriptEngine::getInstance()->evaluate(argv[1], false, "evalLua.lua");
}
ConsoleFunction(evalLuaTF, const char*, 2, 2, "")
{
SimObject *obj;
if (Sim::findObject(argv[1], obj))
{
return ScriptEngine::getInstance()->executef(obj, "testFunction", 123);
}
return "";
}
ConsoleFunction(evalLuaF, const char*, 2, 12, "")
{
ScriptStackValueRef args[MAX_THUNKARGS];
for (int i=1; i<argc; i++)
{
args[i-1] = argv[i];
}
return ScriptEngine::getInstance()->execute(argc-1, args);
}
ConsoleFunction(evalLuaO, const char*, 2, 12, "")
{
SimObject *obj;
if (Sim::findObject(argv[1], obj))
{
ScriptStackValueRef args[MAX_THUNKARGS];
for (int i=2; i<argc; i++)
{
args[i-2] = argv[i];
}
return ScriptEngine::getInstance()->executeOnObject(obj, argc-2, args);
}
return "";
}
ConsoleFunction(executeLua, bool, 2, 2, "")
{
return ScriptEngine::getInstance()->executeFile(argv[1]);
}
//
// Copyright (c) 2014 James S Urquhart. All rights reserved.
//
// 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 "ScriptEngine.h"
ScriptStackValueRef ScriptStackValueRef::valueAtIndex(int index)
{
ScriptStackValueRef newValue;
newValue.type = ScriptEngine::getInstance()->getStack()->getTypeAtIndex(index);
newValue.index = ScriptEngine::getInstance()->getStack()->pushIndex(index);
return newValue;
}
ScriptStack::ScriptStack()
{
}
ScriptStack::~ScriptStack()
{
}
int ScriptStack::pushNull()
{
}
int ScriptStack::pushIndex(int value)
{
}
int ScriptStack::pushInteger(U32 value)
{
}
int ScriptStack::pushSignedInteger(S32 value)
{
}
int ScriptStack::pushNumber(F32 value)
{
}
int ScriptStack::pushSimFields(SimFieldDictionary *values)
{
}
int ScriptStack::pushString(const char *value)
{
}
int ScriptStack::pushSimObject(SimObject *value)
{
}
int ScriptStack::pushBool(bool value)
{
}
// Get values
const char* ScriptStack::getString(int index)
{
}
U32 ScriptStack::getInteger(int index)
{
}
S32 ScriptStack::getSignedInteger(int index)
{
}
F32 ScriptStack::getNumber(int index)
{
}
bool ScriptStack::getBool(int index)
{
}
void ScriptStack::getSimFields(int index, SimFieldDictionary *outValues)
{
}
ScriptValueBaseType ScriptStack::getTypeAtIndex(int index)
{
}
void ScriptStack::setString(int index, const char *value)
{
}
void ScriptStack::setInteger(int index, U32 value)
{
}
void ScriptStack::setSignedInteger(int index, S32 value)
{
}
void ScriptStack::setNumber(int index, F32 value)
{
}
void ScriptStack::setSimFields(int index, SimFieldDictionary *values)
{
}
void ScriptStack::setSimObject(int index, SimObject *value)
{
}
void ScriptStack::setBool(int index, bool value)
{
}
ScriptEngine *sScriptInstance = NULL;
ScriptEngine::ScriptEngine()
{
sScriptInstance = this;
}
ScriptEngine::~ScriptEngine()
{
}
ScriptEngine *ScriptEngine::getInstance()
{
return sScriptInstance;
}
void ScriptEngine::registerClass(AbstractClassRep *rep)
{
}
void ScriptEngine::registerFunctions(Namespace *ns)
{
}
void ScriptEngine::registerObject(SimObject *object)
{
}
void ScriptEngine::removeObject(SimObject *object)
{
}
ScriptStack *ScriptEngine::getStack()
{
return NULL;
}
ScriptStackValueRef ScriptEngine::execute(S32 argc, ScriptStackValueRef argv[])
{
return ScriptStackValueRef();
}
ScriptStackValueRef ScriptEngine::executeOnObject(ScriptStackValueRef obj, S32 argc, ScriptStackValueRef argv[])
{
return ScriptStackValueRef();
}
ScriptStackValueRef ScriptEngine::evaluate(const char* string, bool echo, const char *fileName)
{
}
ScriptStackValueRef ScriptEngine::evaluatef(const char* string, ...)
{
}
// Note: operators replace value
ScriptStackValueRef& ScriptStackValueRef::operator=(const ScriptStackValueRef &other)
{
index = other.index;
type = other.type;
return *this;
}
ScriptStackValueRef& ScriptStackValueRef::operator=(bool newValue)
{
if (index == -1)
index = ScriptEngine::getInstance()->getStack()->pushBool(newValue);
else
ScriptEngine::getInstance()->getStack()->setBool(index, newValue);
return *this;
}
ScriptStackValueRef& ScriptStackValueRef::operator=(const char *newValue)
{
if (index == -1)
index = ScriptEngine::getInstance()->getStack()->pushString(newValue);
else
ScriptEngine::getInstance()->getStack()->setString(index, newValue);
return *this;
}
ScriptStackValueRef& ScriptStackValueRef::operator=(U32 newValue)
{
if (index == -1)
index = ScriptEngine::getInstance()->getStack()->pushInteger(newValue);
else
ScriptEngine::getInstance()->getStack()->setInteger(index, newValue);
return *this;
}
ScriptStackValueRef& ScriptStackValueRef::operator=(S32 newValue)
{
if (index == -1)
index = ScriptEngine::getInstance()->getStack()->pushSignedInteger(newValue);
else
ScriptEngine::getInstance()->getStack()->setSignedInteger(index, newValue);
return *this;
}
ScriptStackValueRef& ScriptStackValueRef::operator=(F32 newValue)
{
if (index == -1)
index = ScriptEngine::getInstance()->getStack()->pushNumber(newValue);
else
ScriptEngine::getInstance()->getStack()->setNumber(index, newValue);
return *this;
}
ScriptStackValueRef& ScriptStackValueRef::operator=(F64 newValue)
{
if (index == -1)
index = ScriptEngine::getInstance()->getStack()->pushNumber(newValue);
else
ScriptEngine::getInstance()->getStack()->setNumber(index, newValue);
return *this;
}
ScriptStackValueRef& ScriptStackValueRef::operator=(SimFieldDictionary *newValue)
{
if (index == -1)
index = ScriptEngine::getInstance()->getStack()->pushSimFields(newValue);
else
ScriptEngine::getInstance()->getStack()->setSimFields(index, newValue);
return *this;
}
ScriptStackValueRef& ScriptStackValueRef::operator=(SimObject *newValue)
{
if (index == -1)
index = ScriptEngine::getInstance()->getStack()->pushSimObject(newValue);
else
ScriptEngine::getInstance()->getStack()->setSimObject(index, newValue);
return *this;
}
ScriptStackValueRef ScriptStackValueRef::clone()
{
ScriptStackValueRef newValue;
newValue.type = type;
newValue.index = index == -1 ? ScriptEngine::getInstance()->getStack()->pushNull() : ScriptEngine::getInstance()->getStack()->pushIndex(index);
return newValue;
}
//
// Copyright (c) 2014 James S Urquhart. All rights reserved.
//
// 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.
//
#ifndef __Torque2D__ScriptEngine__
#define __Torque2D__ScriptEngine__
#include "platform/platform.h"
#include "sim/simFieldDictionary.h"
#include "console/console.h"
class Namespace;
class SimObject;
class AbstractClassRep;
// Basic type for a stack value
typedef enum
{
SCRIPTTYPE_NULL=0,
SCRIPTTYPE_STRING=1,
SCRIPTTYPE_INTEGER=2,
SCRIPTTYPE_NUMBER=3,
SCRIPTTYPE_BOOL=4,
SCRIPTTYPE_TABLE=5, // i.e. a script Dictionary
SCRIPTTYPE_OBJECT=6 // Instance of class
} ScriptValueBaseType;
class ScriptStack
{
public:
ScriptStack();
virtual ~ScriptStack();
// Push values
virtual int pushNull();
virtual int pushBool(bool value);
virtual int pushIndex(int value);
virtual int pushInteger(U32 value);
virtual int pushSignedInteger(S32 value);
virtual int pushNumber(F32 value);
virtual int pushSimFields(SimFieldDictionary *values);
virtual int pushString(const char *value);
virtual int pushSimObject(SimObject *value);
// Get values
virtual const char* getString(int index);
virtual U32 getInteger(int index);
virtual S32 getSignedInteger(int index);
virtual F32 getNumber(int index);
virtual void getSimFields(int index, SimFieldDictionary *outValues);
virtual bool getBool(int index);
virtual ScriptValueBaseType getTypeAtIndex(int index);
// Set values
virtual void setBool(int index, bool value);
virtual void setString(int index, const char *value);
virtual void setInteger(int index, U32 value);
virtual void setSignedInteger(int index, S32 value);
virtual void setNumber(int index, F32 value);
virtual void setSimFields(int index, SimFieldDictionary *values);
virtual void setSimObject(int index, SimObject *value);
};
// A value which has already been pushed to the stack
// e.g.
// ScriptStackValueRef value1 = "carrots";
// ScriptStackValueRef value2 = 123;
// ScriptEngine::getInstance()->call();
// or:
// ScriptEngine::getInstance()->executef("carrots", 123);
class ScriptStackValueRef
{
public:
int index;
ScriptValueBaseType type;
ScriptStackValueRef() : index(-1), type(SCRIPTTYPE_NULL) {;}
~ScriptStackValueRef() {;}
ScriptStackValueRef(const ScriptStackValueRef &ref) : index(ref.index), type(ref.type) { ; }
ScriptStackValueRef(const char *value) : index(-1)
{
*this = value;
}
ScriptStackValueRef(U32 value) : index(-1)
{
*this = value;
}
ScriptStackValueRef(S32 value) : index(-1)
{
*this = value;
}
ScriptStackValueRef(F32 value) : index(-1)
{
*this = value;
}
ScriptStackValueRef(F64 value) : index(-1)
{
*this = value;
}
ScriptStackValueRef(SimObject *value) : index(-1)
{
*this = value;
}
ScriptStackValueRef(bool value) : index(-1)
{
*this = value;
}
ScriptStackValueRef clone();
inline const char* getStringValue();
inline U32 getIntValue();
inline S32 getSignedIntValue();
inline F32 getFloatValue();
inline bool getBoolValue();
inline operator const char*() { return getStringValue(); }
inline operator U32() { return getIntValue(); }
inline operator S32() { return getSignedIntValue(); }
inline operator F32() { return getFloatValue(); }
inline bool isString() { return type == SCRIPTTYPE_STRING; }
inline bool isInt() { return type == SCRIPTTYPE_INTEGER; }
inline bool isFloat() { return type == SCRIPTTYPE_NUMBER; }
inline bool isTable() { return type == SCRIPTTYPE_TABLE; }
inline bool isObject() { return type == SCRIPTTYPE_OBJECT; }
// Note: operators replace value
ScriptStackValueRef& operator=(const ScriptStackValueRef &other);
ScriptStackValueRef& operator=(const char *newValue);
ScriptStackValueRef& operator=(U32 newValue);
ScriptStackValueRef& operator=(S32 newValue);
ScriptStackValueRef& operator=(F32 newValue);
ScriptStackValueRef& operator=(F64 newValue);
ScriptStackValueRef& operator=(SimFieldDictionary *dict);
ScriptStackValueRef& operator=(SimObject *object);
ScriptStackValueRef& operator=(bool newValue);
static ScriptStackValueRef valueAtIndex(int index);
};
class ScriptStackValueRef;
//
class ScriptEngineValueRef
{
public:
virtual ScriptStackValueRef& operator=(const ScriptEngineValueRef &other) { return *this;}
};
class ScriptFunction()
class ScriptEngine
{
public:
ScriptEngine();
virtual ~ScriptEngine();
static ScriptEngine *getInstance();
// iterates and registers bindings
virtual void registerBindings(ConsoleConstructor *root);
// register class
virtual void registerClass(AbstractClassRep *rep);
// register or re-register namespace functions, ensuring proper class heirachy with parent.
virtual void registerFunctions(Namespace *ns);
// register object instance
virtual void registerObject(SimObject *object);
// unregister object instance
virtual void removeObject(SimObject *object);
// gets current stack
virtual ScriptStack *getStack();
virtual ScriptStackValueRef execute(S32 argc, ScriptStackValueRef argv[]);
virtual ScriptStackValueRef executeOnObject(ScriptStackValueRef obj, S32 argc, ScriptStackValueRef argv[]);
#define ARG ScriptStackValueRef
// Plain versions
inline ScriptStackValueRef executef( ARG);
inline ScriptStackValueRef executef( ARG, ARG);
inline ScriptStackValueRef executef( ARG, ARG, ARG);
inline ScriptStackValueRef executef( ARG, ARG, ARG, ARG);
inline ScriptStackValueRef executef( ARG, ARG, ARG, ARG, ARG);
inline ScriptStackValueRef executef( ARG, ARG, ARG, ARG, ARG, ARG);
inline ScriptStackValueRef executef( ARG, ARG, ARG, ARG, ARG, ARG, ARG);
inline ScriptStackValueRef executef( ARG, ARG, ARG, ARG, ARG, ARG, ARG, ARG);
inline ScriptStackValueRef executef( ARG, ARG, ARG, ARG, ARG, ARG, ARG, ARG, ARG);
inline ScriptStackValueRef executef( ARG, ARG, ARG, ARG, ARG, ARG, ARG, ARG, ARG, ARG);
// Object versions
inline ScriptStackValueRef executef(SimObject *, ARG);
inline ScriptStackValueRef executef(SimObject *, ARG, ARG);
inline ScriptStackValueRef executef(SimObject *, ARG, ARG, ARG);
inline ScriptStackValueRef executef(SimObject *, ARG, ARG, ARG, ARG);
inline ScriptStackValueRef executef(SimObject *, ARG, ARG, ARG, ARG, ARG);
inline ScriptStackValueRef executef(SimObject *, ARG, ARG, ARG, ARG, ARG, ARG);
inline ScriptStackValueRef executef(SimObject *, ARG, ARG, ARG, ARG, ARG, ARG, ARG);
inline ScriptStackValueRef executef(SimObject *, ARG, ARG, ARG, ARG, ARG, ARG, ARG, ARG);
inline ScriptStackValueRef executef(SimObject *, ARG, ARG, ARG, ARG, ARG, ARG, ARG, ARG, ARG);
inline ScriptStackValueRef executef(SimObject *, ARG, ARG, ARG, ARG, ARG, ARG, ARG, ARG, ARG, ARG);
inline ScriptStackValueRef executef(SimObject *, ARG, ARG, ARG, ARG, ARG, ARG, ARG, ARG, ARG, ARG, ARG);
#undef ARG
/// Evaluate an arbitrary chunk of code.
///
/// @param string Buffer containing code to execute.
/// @param echo Should we echo the string to the console?
/// @param fileName Indicate what file this code is coming from; used in error reporting and such.
virtual ScriptStackValueRef evaluate(const char* string, bool echo = false, const char *fileName = NULL);
/// Evaluate an arbitrary line of script.
///
/// This wraps dVsprintf(), so you can substitute parameters into the code being executed.
virtual ScriptStackValueRef evaluatef(const char* string, ...);
// Compiles a file to a binary script
virtual bool compileFile(const char *filename) = 0;
// Executes a file to a binary script
virtual bool executeFile(const char *filename) = 0;
};
#define A ScriptStackValueRef
#define OBJ SimObject* obj
inline ScriptStackValueRef ScriptEngine::executef(OBJ, A a) { ScriptStackValueRef params[] = {a}; return executeOnObject(obj, 1, params); }
inline ScriptStackValueRef ScriptEngine::executef(OBJ, A a, A b) { ScriptStackValueRef params[] = {a,b}; return executeOnObject(obj, 2, params); }
inline ScriptStackValueRef ScriptEngine::executef(OBJ, A a, A b, A c) { ScriptStackValueRef params[] = {a,b,c}; return executeOnObject(obj, 3, params); }
inline ScriptStackValueRef ScriptEngine::executef(OBJ, A a, A b, A c, A d) { ScriptStackValueRef params[] = {a,b,c,d}; return executeOnObject(obj, 4, params); }
inline ScriptStackValueRef ScriptEngine::executef(OBJ, A a, A b, A c, A d, A e) { ScriptStackValueRef params[] = {a,b,c,d,e}; return executeOnObject(obj, 5, params); }
inline ScriptStackValueRef ScriptEngine::executef(OBJ, A a, A b, A c, A d, A e, A f) { ScriptStackValueRef params[] = {a,b,c,d,e,f}; return executeOnObject(obj, 6, params); }
inline ScriptStackValueRef ScriptEngine::executef(OBJ, A a, A b, A c, A d, A e, A f, A g) { ScriptStackValueRef params[] = {a,b,c,d,e,f,g}; return executeOnObject(obj, 7, params); }
inline ScriptStackValueRef ScriptEngine::executef(OBJ, A a, A b, A c, A d, A e, A f, A g, A h) { ScriptStackValueRef params[] = {a,b,c,d,e,f,g,h}; return executeOnObject(obj, 8, params); }
inline ScriptStackValueRef ScriptEngine::executef(OBJ, A a, A b, A c, A d, A e, A f, A g, A h, A i) { ScriptStackValueRef params[] = {a,b,c,d,e,f,g,h,i}; return executeOnObject(obj, 9, params); }
inline ScriptStackValueRef ScriptEngine::executef(OBJ, A a, A b, A c, A d, A e, A f, A g, A h, A i, A j) { ScriptStackValueRef params[] = {a,b,c,d,e,f,g,h,i,j}; return executeOnObject(obj, 10, params); }
inline ScriptStackValueRef ScriptEngine::executef(OBJ, A a, A b, A c, A d, A e, A f, A g, A h, A i, A j, A k) { ScriptStackValueRef params[] = {a,b,c,d,e,f,g,h,i,j,k}; return executeOnObject(obj, 11, params); }
inline ScriptStackValueRef ScriptEngine::executef(A a) { ScriptStackValueRef params[] = {a}; return execute(1, params); }
inline ScriptStackValueRef ScriptEngine::executef(A a, A b) { ScriptStackValueRef params[] = {a,b}; return execute(2, params); }
inline ScriptStackValueRef ScriptEngine::executef(A a, A b, A c) { ScriptStackValueRef params[] = {a,b,c}; return execute(3, params); }
inline ScriptStackValueRef ScriptEngine::executef(A a, A b, A c, A d) { ScriptStackValueRef params[] = {a,b,c,d}; return execute(4, params); }
inline ScriptStackValueRef ScriptEngine::executef(A a, A b, A c, A d, A e) { ScriptStackValueRef params[] = {a,b,c,d,e}; return execute(5, params); }
inline ScriptStackValueRef ScriptEngine::executef(A a, A b, A c, A d, A e, A f) { ScriptStackValueRef params[] = {a,b,c,d,e,f}; return execute(6, params); }
inline ScriptStackValueRef ScriptEngine::executef(A a, A b, A c, A d, A e, A f, A g) { ScriptStackValueRef params[] = {a,b,c,d,e,f,g}; return execute(7, params); }
inline ScriptStackValueRef ScriptEngine::executef(A a, A b, A c, A d, A e, A f, A g, A h) { ScriptStackValueRef params[] = {a,b,c,d,e,f,g,h}; return execute(8, params); }
inline ScriptStackValueRef ScriptEngine::executef(A a, A b, A c, A d, A e, A f, A g, A h, A i) { ScriptStackValueRef params[] = {a,b,c,d,e,f,g,h,i}; return execute(9, params); }
inline ScriptStackValueRef ScriptEngine::executef(A a, A b, A c, A d, A e, A f, A g, A h, A i, A j) { ScriptStackValueRef params[] = {a,b,c,d,e,f,g,h,i,j}; return execute(10, params); }
#undef A
#undef OBJ
inline const char* ScriptStackValueRef::getStringValue() { return ScriptEngine::getInstance()->getStack()->getString(index); }
inline U32 ScriptStackValueRef::getIntValue() { return ScriptEngine::getInstance()->getStack()->getInteger(index); }
inline S32 ScriptStackValueRef::getSignedIntValue() { return ScriptEngine::getInstance()->getStack()->getSignedInteger(index); }
inline F32 ScriptStackValueRef::getFloatValue() { return ScriptEngine::getInstance()->getStack()->getNumber(index); }
inline bool ScriptStackValueRef::getBoolValue() { return ScriptEngine::getInstance()->getStack()->getInteger(index); }
#endif /* defined(__Torque2D__ScriptEngine__) */
//-----------------------------------------------------------------------------
// Copyright (c) 2013 GarageGames, LLC
//
// 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 "collection/findIterator.h"
#include "io/resource/resourceManager.h"
#include "string/findMatch.h"
#include "string/stringUnit.h"
#include "console/consoleInternal.h"
#include "io/fileStream.h"
#include "console/compiler.h"
#include "sim/simBase.h"
#include "network/netStringTable.h"
#include "component/dynamicConsoleMethodComponent.h"
#include "string/stringStack.h"
#include "messaging/message.h"
#include "memory/frameAllocator.h"
#include "debug/telnetDebugger.h"
#ifndef _REMOTE_DEBUGGER_BASE_H_
#include "debug/remote/RemoteDebuggerBase.h"
#endif
#include "script/ScriptEngine.h"
#include "script/TSScriptEngine.h"
using namespace Compiler;
static 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);
if(walk->mParent)
dStrcat(ret, " -> ");
}
return ret;
}
//------------------------------------------------------------
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;
}
//------------------------------------------------------------
// TODO: ABSTRACT
namespace Con
{
char *getReturnBuffer(U32 bufferSize)
{
return STR.getReturnBuffer(bufferSize);
}
char *getReturnBuffer( const char *stringToCopy )
{
char *ret = STR.getReturnBuffer( dStrlen( stringToCopy ) + 1 );
dStrcpy( ret, stringToCopy );
ret[dStrlen( stringToCopy )] = '\0';
return ret;
}
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;
}
}
//------------------------------------------------------------
inline void ExprEvalState::setCurVarName(StringTableEntry name)
{
if(name[0] == '$')
currentVariable = globalVars.lookup(name);
else if(stack.size())
currentVariable = stack.last()->lookup(name);
if(!currentVariable && gWarnUndefinedScriptVariables)
Con::warnf(ConsoleLogEntry::Script, "Variable referenced before assignment: %s", name);
}
inline void ExprEvalState::setCurVarNameCreate(StringTableEntry name)
{
if(name[0] == '$')
currentVariable = globalVars.add(name);
else if(stack.size())
currentVariable = stack.last()->add(name);
else
{
currentVariable = NULL;
Con::warnf(ConsoleLogEntry::Script, "Accessing local variable in global scope... failed: %s", name);
}
}
//------------------------------------------------------------
inline S32 ExprEvalState::getIntVariable()
{
return currentVariable ? currentVariable->getIntValue() : 0;
}
inline F64 ExprEvalState::getFloatVariable()
{
return currentVariable ? currentVariable->getFloatValue() : 0;
}
inline const char *ExprEvalState::getStringVariable()
{
return currentVariable ? currentVariable->getStringValue() : "";
}
//------------------------------------------------------------
inline void ExprEvalState::setIntVariable(S32 val)
{
AssertFatal(currentVariable != NULL, "Invalid evaluator state - trying to set null variable!");
currentVariable->setIntValue(val);
}
inline void ExprEvalState::setFloatVariable(F64 val)
{
AssertFatal(currentVariable != NULL, "Invalid evaluator state - trying to set null variable!");
currentVariable->setFloatValue((F32)val);
}
inline void ExprEvalState::setStringVariable(const char *val)
{
AssertFatal(currentVariable != NULL, "Invalid evaluator state - trying to set null variable!");
currentVariable->setStringValue(val);
}
//------------------------------------------------------------
void CodeBlock::getFunctionArgs(char buffer[1024], U32 ip)
{
U32 fnArgc = code[ip + 5];
buffer[0] = 0;
for(U32 i = 0; i < fnArgc; i++)
{
StringTableEntry var = CodeToSTE(code, ip + (i*2) + 6);
// Add a comma so it looks nice!
if(i != 0)
dStrcat(buffer, ", ");
dStrcat(buffer, "var ");
// Try to capture junked parameters
if(var[0])
dStrcat(buffer, var+1);
else
dStrcat(buffer, "JUNK");
}
}
// Returns, in 'val', the specified component of a string.
static void getUnit(const char *string, U32 index, const char *set, char val[], S32 len)
{
U32 sz;
while(index--)
{
if(!*string)
return;
sz = dStrcspn(string, set);
if (string[sz] == 0)
return;
string += (sz + 1);
}
sz = dStrcspn(string, set);
if (sz == 0)
return;
if( ( sz + 1 ) > (U32)len )
return;
dStrncpy(val, string, sz);
val[sz] = '\0';
}
// Copies a string, replacing the (space separated) specified component. The
// return value is stored in 'val'.
static void setUnit(const char *string, U32 index, const char *replace, const char *set, char val[], S32 len)
{
U32 sz;
const char *start = string;
if( ( dStrlen(string) + dStrlen(replace) + 1 ) > (U32)len )
return;
U32 padCount = 0;
while(index--)
{
sz = dStrcspn(string, set);
if(string[sz] == 0)
{
string += sz;
padCount = index + 1;
break;
}
else
string += (sz + 1);
}
// copy first chunk
sz = (U32)(string-start);
dStrncpy(val, start, sz);
for(U32 i = 0; i < padCount; i++)
val[sz++] = set[0];
// replace this unit
val[sz] = '\0';
dStrcat(val, replace);
// copy remaining chunks
sz = dStrcspn(string, set); // skip chunk we're replacing
if(!sz && !string[sz])
return;
string += sz;
dStrcat(val, string);
return;
}
//-----------------------------------------------------------------------------
static bool isDigitsOnly( const char* pString )
{
// Sanity.
AssertFatal( pString != NULL, "isDigits() - Cannot check a NULL string." );
const char* pDigitCursor = pString;
if ( *pDigitCursor == 0 )
return false;
// Check for digits only.
do
{
if ( dIsdigit( *pDigitCursor++ ) )
continue;
return false;
}
while( *pDigitCursor != 0 );
return true;
}
//-----------------------------------------------------------------------------
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" )
};
static const StringTableEntry _size[] =
{
StringTable->insert( "width" ),
StringTable->insert( "height" )
};
static const StringTableEntry _count = StringTable->insert( "count" );
//-----------------------------------------------------------------------------
// Gets a component of an object's field value or a variable and returns it in val.
static void getFieldComponent( SimObject* object, StringTableEntry field, const char* array, StringTableEntry subField, char* val, const U32 bufferSize )
{
const char* prevVal = NULL;
// Grab value from object.
if( object && field )
prevVal = object->getDataField( field, array );
// Otherwise, grab from the string stack. The value coming in will always
// be a string because that is how multi-component variables are handled.
else
prevVal = STR.getStringValue();
// Make sure we got a value.
if ( prevVal && *prevVal )
{
if ( subField == _count )
dSprintf( val, bufferSize, "%d", StringUnit::getUnitCount( prevVal, " \t\n" ) );
else if ( subField == _xyzw[0] || subField == _rgba[0] || subField == _size[0] )
dStrncpy( val, StringUnit::getUnit( prevVal, 0, " \t\n"), bufferSize );
else if ( subField == _xyzw[1] || subField == _rgba[1] || subField == _size[1] )
dStrncpy( val, StringUnit::getUnit( prevVal, 1, " \t\n"), bufferSize );
else if ( subField == _xyzw[2] || subField == _rgba[2] )
dStrncpy( val, StringUnit::getUnit( prevVal, 2, " \t\n"), bufferSize );
else if ( subField == _xyzw[3] || subField == _rgba[3] )
dStrncpy( val, StringUnit::getUnit( prevVal, 3, " \t\n"), bufferSize );
else if ( *subField == '_' && isDigitsOnly(subField+1) )
dStrncpy( val, StringUnit::getUnit( prevVal, dAtoi(subField+1), " \t\n"), bufferSize );
else
val[0] = 0;
}
else
val[0] = 0;
}
//-----------------------------------------------------------------------------
// Sets a component of an object's field value based on the sub field. 'x' will
// set the first field, 'y' the second, and 'z' the third.
void TSScriptEngine::setFieldComponent( SimObject* object, StringTableEntry field, const char* array, StringTableEntry subField )
{
// Copy the current string value
char strValue[1024];
dStrncpy( strValue, STR.getStringValue(), sizeof(strValue) );
char val[1024] = "";
const U32 bufferSize = sizeof(val);
const char* prevVal = NULL;
// Set the value on an object field.
if( object && field )
prevVal = object->getDataField( field, array );
// Set the value on a variable.
else if( gEvalState.currentVariable )
prevVal = gEvalState.getStringVariable();
// Ensure that the variable has a value
if (!prevVal)
return;
if ( subField == _xyzw[0] || subField == _rgba[0] || subField == _size[0] )
dStrncpy( val, StringUnit::setUnit( prevVal, 0, strValue, " \t\n"), bufferSize );
else if ( subField == _xyzw[1] || subField == _rgba[1] || subField == _size[1] )
dStrncpy( val, StringUnit::setUnit( prevVal, 1, strValue, " \t\n"), bufferSize );
else if ( subField == _xyzw[2] || subField == _rgba[2] )
dStrncpy( val, StringUnit::setUnit( prevVal, 2, strValue, " \t\n"), bufferSize );
else if ( subField == _xyzw[3] || subField == _rgba[3] )
dStrncpy( val, StringUnit::setUnit( prevVal, 3, strValue, " \t\n"), bufferSize );
else if ( *subField == '_' && isDigitsOnly(subField+1) )
dStrncpy( val, StringUnit::setUnit( prevVal, dAtoi(subField+1), strValue, " \t\n"), bufferSize );
if ( val[0] != 0 )
{
// Update the field or variable.
if( object && field )
object->setDataField( field, 0, val );
else if( gEvalState.currentVariable )
gEvalState.setStringVariable( val );
}
}
const char *CodeBlock::exec(U32 ip, const char *functionName, Namespace *thisNamespace, U32 argc, const char **argv, bool noCalls, StringTableEntry packageName, S32 setFrame)
{
StringStack &STR = mScriptEngine->STR;
#ifdef TORQUE_DEBUG
U32 stackStart = STR.mStartStackSize;
#endif
static char traceBuffer[1024];
U32 i;
U32& UINT = mScriptEngine->UINT;
U32& FLT = mScriptEngine->FLT;
F64 *floatStack = mScriptEngine->floatStack;
S64 *intStack = mScriptEngine->intStack;
incRefCount();
F64 *curFloatTable;
char *curStringTable;
STR.clearFunctionOffset();
StringTableEntry thisFunctionName = NULL;
bool popFrame = false;
if(argv)
{
// assume this points into a function decl:
U32 fnArgc = code[ip + 2 + 6];
thisFunctionName = CodeToSTE(code, ip);
argc = getMin(argc-1, fnArgc); // argv[0] is func name
if(gEvalState.traceOn)
{
traceBuffer[0] = 0;
dStrcat(traceBuffer, "Entering ");
if(packageName)
{
dStrcat(traceBuffer, "[");
dStrcat(traceBuffer, packageName);
dStrcat(traceBuffer, "]");
}
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 < argc; i++)
{
dStrcat(traceBuffer, argv[i+1]);
if(i != argc - 1)
dStrcat(traceBuffer, ", ");
}
dStrcat(traceBuffer, ")");
Con::printf("%s", traceBuffer);
}
gEvalState.pushFrame(thisFunctionName, thisNamespace);
popFrame = true;
for(i = 0; i < argc; i++)
{
StringTableEntry var = CodeToSTE(code, ip + (2 + 6 + 1) + (i * 2));
gEvalState.setCurVarNameCreate(var);
gEvalState.setStringVariable(argv[i+1]);
}
ip = ip + (fnArgc * 2) + (2 + 6 + 1);
curFloatTable = functionFloats;
curStringTable = functionStrings;
}
else
{
curFloatTable = globalFloats;
curStringTable = globalStrings;
// Do we want this code to execute using a new stack frame?
if (setFrame < 0)
{
gEvalState.pushFrame(NULL, NULL);
popFrame = true;
}
else if (!gEvalState.stack.empty())
{
// 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.stack.size() - 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();
// Notify the remote debugger.
RemoteDebuggerBase* pRemoteDebugger = RemoteDebuggerBase::getRemoteDebugger();
if ( pRemoteDebugger != NULL && setFrame < 0 )
pRemoteDebugger->pushStackFrame();
StringTableEntry var, objParent;
U32 failJump;
StringTableEntry fnName;
StringTableEntry fnNamespace, fnPackage;
SimObject *currentNewObject = 0;
StringTableEntry prevField = NULL;
StringTableEntry curField = NULL;
SimObject *prevObject = NULL;
SimObject *curObject = NULL;
SimObject *saveObject=NULL;
Namespace::Entry *nsEntry;
Namespace *ns;
const char* curFNDocBlock = NULL;
const char* curNSDocBlock = NULL;
const S32 nsDocLength = 128;
char nsDocBlockClass[nsDocLength];
U32 callArgc;
const char **callArgv;
static char curFieldArray[256];
static char prevFieldArray[256];
CodeBlock *saveCodeBlock = mScriptEngine->smCurrentCodeBlock;
mScriptEngine->smCurrentCodeBlock = this;
if(this->name)
{
mScriptEngine->gCurrentFile = this->name;
mScriptEngine->gCurrentRoot = mRoot;
}
const char * val;
// 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<char> 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 = bool(code[ip+6]);
Namespace::unlinkPackages();
ns = Namespace::find(fnNamespace, fnPackage);
ns->addFunction(fnName, this, hasBody ? ip : 0, curFNDocBlock ? dStrdup( curFNDocBlock ) : NULL );// 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 isMessage = code[ip + 4];
failJump = code[ip + 5];
// 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;
}
// Get the constructor information off the stack.
STR.getArgcArgv(NULL, &callArgc, &callArgv, true);
// 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(callArgv[2]);
// Make sure we're not changing types on ourselves...
if(db && dStricmp(db->getClassName(), callArgv[1]))
{
Con::errorf(ConsoleLogEntry::General, "Cannot re-declare data block %s with a different class.", callArgv[2]);
ip = failJump;
break;
}
// If there was one, set the currentNewObject and move on.
if(db)
currentNewObject = db;
}
if(!currentNewObject)
{
// Well, looks like we have to create a new object.
ConsoleObject *object = ConsoleObject::create(callArgv[1]);
// Deal with failure!
if(!object)
{
Con::errorf(ConsoleLogEntry::General, "%s: Unable to instantiate non-conobject class %s.", getFileLine(ip-1), callArgv[1]);
ip = failJump;
break;
}
// Do special datablock init if appropros
if(isDataBlock)
{
SimDataBlock *dataBlock = dynamic_cast<SimDataBlock *>(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]);
// Clean up...
delete object;
ip = failJump;
break;
}
}
// Finally, set currentNewObject to point to the new one.
currentNewObject = dynamic_cast<SimObject *>(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]);
delete object;
ip = failJump;
break;
}
// Does it have a parent object? (ie, the copy constructor : syntax, not inheriance)
// [tom, 9/8/2006] it is inheritance if it's a message ... muwahahah!
if(!isMessage && *objParent)
{
// Find it!
SimObject *parent;
if(Sim::findObject(objParent, parent))
{
// Con::printf(" - Parent object found: %s", parent->getClassName());
// and suck the juices from it!
currentNewObject->assignFieldsFrom(parent);
}
else
Con::errorf(ConsoleLogEntry::General, "%s: Unable to find parent object %s for %s.", getFileLine(ip-1), objParent, callArgv[1]);
// Mm! Juices!
}
// If a name was passed, assign it.
if(callArgv[2][0])
{
if(! isMessage)
{
if(! isInternal)
currentNewObject->assignName(callArgv[2]);
else
currentNewObject->setInternalName(callArgv[2]);
}
else
{
Message *msg = dynamic_cast<Message *>(currentNewObject);
if(msg)
{
msg->setClassNamespace(callArgv[2]);
msg->setSuperClassNamespace(objParent);
}
else
{
Con::errorf(ConsoleLogEntry::General, "%s: Attempting to use newmsg on non-message type %s", getFileLine(ip-1), callArgv[1]);
delete currentNewObject;
currentNewObject = NULL;
ip = failJump;
break;
}
}
}
// 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);
}
}
// Advance the IP past the create info...
ip += 6;
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;
}
if(currentNewObject->isProperlyAdded() == false)
{
bool ret = false;
Message *msg = dynamic_cast<Message *>(currentNewObject);
if(msg)
{
SimObjectId id = Message::getNextMessageID();
if(id != 0xffffffff)
ret = currentNewObject->registerObject(id);
else
Con::errorf("%s: No more object IDs available for messages", getFileLine(ip-2));
}
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;
ip = failJump;
break;
}
}
// Are we dealing with a datablock?
SimDataBlock *dataBlock = dynamic_cast<SimDataBlock *>(currentNewObject);
static char errorBuffer[256];
// If so, preload it.
if(dataBlock && !dataBlock->preload(true, errorBuffer))
{
Con::errorf(ConsoleLogEntry::General, "%s: preload failed for %s: %s.", getFileLine(ip-2),
currentNewObject->getName(), errorBuffer);
dataBlock->deleteObject();
ip = failJump;
break;
}
// What group will we be added to, if any?
U32 groupAddId = (U32)intStack[UINT];
SimGroup *grp = NULL;
SimSet *set = NULL;
SimComponent *comp = NULL;
bool isMessage = dynamic_cast<Message *>(currentNewObject) != NULL;
if(!placeAtRoot || !currentNewObject->getGroup())
{
if(! isMessage)
{
if(! placeAtRoot)
{
// Otherwise just add to the requested group or set.
if(!Sim::findObject(groupAddId, grp))
if(!Sim::findObject(groupAddId, comp))
Sim::findObject(groupAddId, set);
}
if(placeAtRoot || comp != NULL)
{
// Deal with the instantGroup if we're being put at the root or we're adding to a component.
const char *addGroupName = Con::getVariable("instantGroup");
if(!Sim::findObject(addGroupName, grp))
Sim::findObject(RootGroupId, grp);
}
if(comp)
{
SimComponent *newComp = dynamic_cast<SimComponent *>(currentNewObject);
if(newComp)
{
if(! comp->addComponent(newComp))
Con::errorf("%s: Unable to add component %s, template not loaded?", getFileLine(ip-2), currentNewObject->getName() ? currentNewObject->getName() : currentNewObject->getIdString());
}
}
}
// If we didn't get a group, then make sure we have a pointer to
// the rootgroup.
if(!grp)
Sim::findObject(RootGroupId, grp);
// add to the parent group
grp->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)
if(placeAtRoot)
intStack[UINT] = currentNewObject->getId();
else
intStack[++UINT] = currentNewObject->getId();
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)
UINT--;
break;
}
case OP_JMPIFFNOT:
if(floatStack[FLT--])
{
ip++;
break;
}
ip = code[ip];
break;
case OP_JMPIFNOT:
if(intStack[UINT--])
{
ip++;
break;
}
ip = code[ip];
break;
case OP_JMPIFF:
if(!floatStack[FLT--])
{
ip++;
break;
}
ip = code[ip];
break;
case OP_JMPIF:
if(!intStack[UINT--])
{
ip ++;
break;
}
ip = code[ip];
break;
case OP_JMPIFNOT_NP:
if(intStack[UINT])
{
UINT--;
ip++;
break;
}
ip = code[ip];
break;
case OP_JMPIF_NP:
if(!intStack[UINT])
{
UINT--;
ip++;
break;
}
ip = code[ip];
break;
case OP_JMP:
ip = code[ip];
break;
case OP_RETURN:
goto execFinished;
case OP_CMPEQ:
intStack[UINT+1] = bool(floatStack[FLT] == floatStack[FLT-1]);
UINT++;
FLT -= 2;
break;
case OP_CMPGR:
intStack[UINT+1] = bool(floatStack[FLT] > floatStack[FLT-1]);
UINT++;
FLT -= 2;
break;
case OP_CMPGE:
intStack[UINT+1] = bool(floatStack[FLT] >= floatStack[FLT-1]);
UINT++;
FLT -= 2;
break;
case OP_CMPLT:
intStack[UINT+1] = bool(floatStack[FLT] < floatStack[FLT-1]);
UINT++;
FLT -= 2;
break;
case OP_CMPLE:
intStack[UINT+1] = bool(floatStack[FLT] <= floatStack[FLT-1]);
UINT++;
FLT -= 2;
break;
case OP_CMPNE:
intStack[UINT+1] = bool(floatStack[FLT] != floatStack[FLT-1]);
UINT++;
FLT -= 2;
break;
case OP_XOR:
intStack[UINT-1] = intStack[UINT] ^ intStack[UINT-1];
UINT--;
break;
case OP_MOD:
if( intStack[UINT-1] != 0 )
intStack[UINT-1] = intStack[UINT] % intStack[UINT-1];
else
intStack[UINT-1] = 0;
UINT--;
break;
case OP_BITAND:
intStack[UINT-1] = intStack[UINT] & intStack[UINT-1];
UINT--;
break;
case OP_BITOR:
intStack[UINT-1] = intStack[UINT] | intStack[UINT-1];
UINT--;
break;
case OP_NOT:
intStack[UINT] = !intStack[UINT];
break;
case OP_NOTF:
intStack[UINT+1] = !floatStack[FLT];
FLT--;
UINT++;
break;
case OP_ONESCOMPLEMENT:
intStack[UINT] = ~intStack[UINT];
break;
case OP_SHR:
intStack[UINT-1] = intStack[UINT] >> intStack[UINT-1];
UINT--;
break;
case OP_SHL:
intStack[UINT-1] = intStack[UINT] << intStack[UINT-1];
UINT--;
break;
case OP_AND:
intStack[UINT-1] = intStack[UINT] && intStack[UINT-1];
UINT--;
break;
case OP_OR:
intStack[UINT-1] = intStack[UINT] || intStack[UINT-1];
UINT--;
break;
case OP_ADD:
floatStack[FLT-1] = floatStack[FLT] + floatStack[FLT-1];
FLT--;
break;
case OP_SUB:
floatStack[FLT-1] = floatStack[FLT] - floatStack[FLT-1];
FLT--;
break;
case OP_MUL:
floatStack[FLT-1] = floatStack[FLT] * floatStack[FLT-1];
FLT--;
break;
case OP_DIV:
floatStack[FLT-1] = floatStack[FLT] / floatStack[FLT-1];
FLT--;
break;
case OP_NEG:
floatStack[FLT] = -floatStack[FLT];
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;
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;
gEvalState.setCurVarNameCreate(var);
// See OP_SETCURVAR for why we do this.
curFNDocBlock = NULL;
curNSDocBlock = NULL;
break;
case OP_SETCURVAR_ARRAY:
var = STR.getSTValue();
// See OP_SETCURVAR
prevField = NULL;
prevObject = NULL;
curObject = NULL;
gEvalState.setCurVarName(var);
// See OP_SETCURVAR for why we do this.
curFNDocBlock = NULL;
curNSDocBlock = NULL;
break;
case OP_SETCURVAR_ARRAY_CREATE:
var = STR.getSTValue();
// See OP_SETCURVAR
prevField = NULL;
prevObject = NULL;
curObject = NULL;
gEvalState.setCurVarNameCreate(var);
// See OP_SETCURVAR for why we do this.
curFNDocBlock = NULL;
curNSDocBlock = NULL;
break;
case OP_LOADVAR_UINT:
intStack[UINT+1] = gEvalState.getIntVariable();
UINT++;
break;
case OP_LOADVAR_FLT:
floatStack[FLT+1] = gEvalState.getFloatVariable();
FLT++;
break;
case OP_LOADVAR_STR:
val = gEvalState.getStringVariable();
STR.setStringValue(val);
break;
case OP_SAVEVAR_UINT:
gEvalState.setIntVariable((S32)intStack[UINT]);
break;
case OP_SAVEVAR_FLT:
gEvalState.setFloatVariable(floatStack[FLT]);
break;
case OP_SAVEVAR_STR:
gEvalState.setStringVariable(STR.getStringValue());
break;
case OP_SETCUROBJECT:
// Save the previous object for parsing vector fields.
prevObject = curObject;
val = STR.getStringValue();
// 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)
{
SimGroup *group = dynamic_cast<SimGroup *>(curObject);
if(group)
{
StringTableEntry intName = StringTable->insert(STR.getStringValue());
bool recurse = code[ip-1];
SimObject *obj = group->findObjectByInternalName(intName, recurse);
intStack[UINT+1] = obj ? obj->getId() : 0;
UINT++;
}
else
{
Con::errorf(ConsoleLogEntry::Script, "%s: Attempt to use -> on non-group %s of class %s.", getFileLine(ip-2), curObject->getName(), curObject->getClassName());
intStack[UINT] = 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 );
curField = CodeToSTE(code, ip);
curFieldArray[0] = 0;
ip += 2;
break;
case OP_SETCURFIELD_ARRAY:
dStrcpy(curFieldArray, STR.getStringValue());
break;
case OP_LOADFIELD_UINT:
if(curObject)
intStack[UINT+1] = U32(dAtoi(curObject->getDataField(curField, curFieldArray)));
else
{
// The field is not being retrieved from an object. Maybe it's
// a special accessor?
getFieldComponent( prevObject, prevField, prevFieldArray, curField, valBuffer, VAL_BUFFER_SIZE );
intStack[UINT+1] = dAtoi( valBuffer );
}
UINT++;
break;
case OP_LOADFIELD_FLT:
if(curObject)
floatStack[FLT+1] = dAtof(curObject->getDataField(curField, curFieldArray));
else
{
// The field is not being retrieved from an object. Maybe it's
// a special accessor?
getFieldComponent( prevObject, prevField, prevFieldArray, curField, valBuffer, VAL_BUFFER_SIZE );
floatStack[FLT+1] = dAtof( valBuffer );
}
FLT++;
break;
case OP_LOADFIELD_STR:
if(curObject)
{
val = curObject->getDataField(curField, curFieldArray);
STR.setStringValue( val );
}
else
{
// The field is not being retrieved from an object. Maybe it's
// a special accessor?
getFieldComponent( prevObject, prevField, prevFieldArray, curField, valBuffer, VAL_BUFFER_SIZE );
STR.setStringValue( valBuffer );
}
break;
case OP_SAVEFIELD_UINT:
STR.setIntValue((U32)intStack[UINT]);
if(curObject)
curObject->setDataField(curField, curFieldArray, STR.getStringValue());
else
{
// The field is not being set on an object. Maybe it's
// a special accessor?
mScriptEngine->setFieldComponent( prevObject, prevField, prevFieldArray, curField );
prevObject = NULL;
}
break;
case OP_SAVEFIELD_FLT:
STR.setFloatValue(floatStack[FLT]);
if(curObject)
curObject->setDataField(curField, curFieldArray, STR.getStringValue());
else
{
// The field is not being set on an object. Maybe it's
// a special accessor?
mScriptEngine->setFieldComponent( prevObject, prevField, prevFieldArray, curField );
prevObject = NULL;
}
break;
case OP_SAVEFIELD_STR:
if(curObject)
curObject->setDataField(curField, curFieldArray, STR.getStringValue());
else
{
// The field is not being set on an object. Maybe it's
// a special accessor?
mScriptEngine->setFieldComponent( prevObject, prevField, prevFieldArray, curField );
prevObject = NULL;
}
break;
case OP_STR_TO_UINT:
intStack[UINT+1] = STR.getIntValue();
UINT++;
break;
case OP_STR_TO_FLT:
floatStack[FLT+1] = STR.getFloatValue();
FLT++;
break;
case OP_STR_TO_NONE:
// This exists simply to deal with certain typecast situations.
break;
case OP_FLT_TO_UINT:
intStack[UINT+1] = (S64)floatStack[FLT];
FLT--;
UINT++;
break;
case OP_FLT_TO_STR:
STR.setFloatValue(floatStack[FLT]);
FLT--;
break;
case OP_FLT_TO_NONE:
FLT--;
break;
case OP_UINT_TO_FLT:
floatStack[FLT+1] = (F64)intStack[UINT];
UINT--;
FLT++;
break;
case OP_UINT_TO_STR:
STR.setIntValue((U32)intStack[UINT]);
UINT--;
break;
case OP_UINT_TO_NONE:
UINT--;
break;
case OP_LOADIMMED_UINT:
intStack[UINT+1] = code[ip++];
UINT++;
break;
case OP_LOADIMMED_FLT:
floatStack[FLT+1] = curFloatTable[code[ip]];
ip++;
FLT++;
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;
}
case OP_LOADIMMED_STR:
STR.setStringValue(curStringTable + code[ip++]);
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:
STR.setStringValue(CodeToSTE(code, ip));
ip += 2;
break;
case OP_CALLFUNC_RESOLVE:
// This deals with a function that is potentially living in a namespace.
fnNamespace = CodeToSTE(code, ip+2);
fnName = CodeToSTE(code, ip);
// Try to look it up.
ns = Namespace::find(fnNamespace);
nsEntry = ns->lookup(fnName);
if(!nsEntry)
{
ip+= 5;
Con::warnf(ConsoleLogEntry::General,
"%s: Unable to find function %s%s%s",
getFileLine(ip-4), fnNamespace ? fnNamespace : "",
fnNamespace ? "::" : "", fnName);
STR.popFrame();
break;
}
// Now, rewrite our code a bit (ie, avoid future lookups) and fall
// through to OP_CALLFUNC
#ifdef TORQUE_64
*((U64*)(code+ip+2)) = ((U64)nsEntry);
#else
code[ip+2] = ((U32)nsEntry);
#endif
code[ip-1] = OP_CALLFUNC;
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.
S32 routingId = 0;
fnName = CodeToSTE(code, ip);
//if this is called from inside a function, append the ip and codeptr
if (!gEvalState.stack.empty())
{
gEvalState.stack.last()->code = this;
gEvalState.stack.last()->ip = ip - 1;
}
U32 callType = code[ip+4];
ip += 5;
STR.getArgcArgv(fnName, &callArgc, &callArgv);
if(callType == FuncCallExprNode::FunctionCall)
{
#ifdef TORQUE_64
nsEntry = ((Namespace::Entry *) *((U64*)(code+ip-3)));
#else
nsEntry = ((Namespace::Entry *) *(code+ip-3));
#endif
ns = NULL;
}
else if(callType == FuncCallExprNode::MethodCall)
{
saveObject = gEvalState.thisObject;
gEvalState.thisObject = Sim::findObject(callArgv[1]);
if(!gEvalState.thisObject)
{
gEvalState.thisObject = 0;
Con::warnf(ConsoleLogEntry::General,"%s: Unable to find object: '%s' attempting to call function '%s'", getFileLine(ip-6), callArgv[1], fnName);
STR.popFrame(); // [neo, 5/7/2007 - #2974]
break;
}
bool handlesMethod = gEvalState.thisObject->handlesConsoleMethod(fnName,&routingId);
if( handlesMethod && routingId == MethodOnComponent )
{
DynamicConsoleMethodComponent *pComponent = dynamic_cast<DynamicConsoleMethodComponent*>( gEvalState.thisObject );
if( pComponent )
pComponent->callMethodArgList( callArgc, callArgv, false );
}
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;
}
}
S32 nsType = -1;
S32 nsMinArgs = 0;
S32 nsMaxArgs = 0;
Namespace::Entry::CallbackUnion * nsCb = NULL;
//Namespace::Entry::CallbackUnion cbu;
const char * nsUsage = NULL;
if (nsEntry)
{
nsType = nsEntry->mType;
nsMinArgs = nsEntry->mMinArgs;
nsMaxArgs = nsEntry->mMaxArgs;
nsCb = &nsEntry->cb;
nsUsage = nsEntry->mUsage;
routingId = 0;
}
if(!nsEntry || noCalls)
{
if(!noCalls && !( routingId == MethodOnComponent ) )
{
Con::warnf(ConsoleLogEntry::General,"%s: Unknown command %s.", getFileLine(ip-6), fnName);
if(callType == FuncCallExprNode::MethodCall)
{
Con::warnf(ConsoleLogEntry::General, " Object %s(%d) %s",
gEvalState.thisObject->getName() ? gEvalState.thisObject->getName() : "",
gEvalState.thisObject->getId(), getNamespaceList(ns) );
}
}
STR.popFrame();
STR.setStringValue("");
break;
}
if(nsEntry->mType == Namespace::Entry::ScriptFunctionType)
{
const char *ret = "";
if(nsEntry->mFunctionOffset)
ret = nsEntry->mCode->exec(nsEntry->mFunctionOffset, fnName, nsEntry->mNamespace, callArgc, callArgv, false, nsEntry->mPackage);
STR.popFrame();
STR.setStringValue(ret);
}
else
{
const char* nsName = ns? ns->mName: "";
if((nsEntry->mMinArgs && S32(callArgc) < nsEntry->mMinArgs) || (nsEntry->mMaxArgs && S32(callArgc) > nsEntry->mMaxArgs))
{
Con::warnf(ConsoleLogEntry::Script, "%s: %s::%s - wrong number of arguments.", getFileLine(ip-6), nsName, fnName);
Con::warnf(ConsoleLogEntry::Script, "%s: usage: %s", getFileLine(ip-4), nsEntry->mUsage);
STR.popFrame();
}
else
{
switch(nsEntry->mType)
{
case Namespace::Entry::StringCallbackType:
{
const char *ret = nsEntry->cb.mStringCallbackFunc(gEvalState.thisObject, callArgc, callArgv);
STR.popFrame();
if(ret != STR.getStringValue())
STR.setStringValue(ret);
else
STR.setLen(dStrlen(ret));
break;
}
case Namespace::Entry::IntCallbackType:
{
S32 result = nsEntry->cb.mIntCallbackFunc(gEvalState.thisObject, callArgc, callArgv);
STR.popFrame();
if(code[ip] == OP_STR_TO_UINT)
{
ip++;
intStack[++UINT] = result;
break;
}
else if(code[ip] == OP_STR_TO_FLT)
{
ip++;
floatStack[++FLT] = result;
break;
}
else if(code[ip] == OP_STR_TO_NONE)
ip++;
else
STR.setIntValue(result);
break;
}
case Namespace::Entry::FloatCallbackType:
{
F64 result = nsEntry->cb.mFloatCallbackFunc(gEvalState.thisObject, callArgc, callArgv);
STR.popFrame();
if(code[ip] == OP_STR_TO_UINT)
{
ip++;
intStack[++UINT] = (S64)result;
break;
}
else if(code[ip] == OP_STR_TO_FLT)
{
ip++;
floatStack[++FLT] = result;
break;
}
else if(code[ip] == OP_STR_TO_NONE)
ip++;
else
STR.setFloatValue(result);
break;
}
case Namespace::Entry::VoidCallbackType:
nsEntry->cb.mVoidCallbackFunc(gEvalState.thisObject, callArgc, callArgv);
if(code[ip] != OP_STR_TO_NONE)
Con::warnf(ConsoleLogEntry::General, "%s: Call to %s in %s uses result of void function call.", getFileLine(ip-6), fnName, functionName);
STR.popFrame();
STR.setStringValue("");
break;
case Namespace::Entry::BoolCallbackType:
{
bool result = nsEntry->cb.mBoolCallbackFunc(gEvalState.thisObject, callArgc, callArgv);
STR.popFrame();
if(code[ip] == OP_STR_TO_UINT)
{
ip++;
intStack[++UINT] = result;
break;
}
else if(code[ip] == OP_STR_TO_FLT)
{
ip++;
floatStack[++FLT] = result;
break;
}
else if(code[ip] == OP_STR_TO_NONE)
ip++;
else
STR.setIntValue(result);
break;
}
}
}
}
if(callType == FuncCallExprNode::MethodCall)
gEvalState.thisObject = saveObject;
break;
}
case OP_ADVANCE_STR:
STR.advance();
break;
case OP_ADVANCE_STR_APPENDCHAR:
STR.advanceChar(code[ip++]);
break;
case OP_ADVANCE_STR_COMMA:
STR.advanceChar('_');
break;
case OP_ADVANCE_STR_NUL:
STR.advanceChar(0);
break;
case OP_REWIND_STR:
STR.rewind();
break;
case OP_TERMINATE_REWIND_STR:
STR.rewindTerminate();
break;
case OP_COMPARE_STR:
intStack[++UINT] = STR.compare();
break;
case OP_PUSH:
STR.push();
break;
case OP_PUSH_FRAME:
STR.pushFrame();
break;
case OP_BREAK:
{
//append the ip and codeptr before managing the breakpoint!
AssertFatal( !gEvalState.stack.empty(), "Empty eval stack on break!");
gEvalState.stack.last()->code = this;
gEvalState.stack.last()->ip = ip - 1;
U32 breakLine;
findBreakLine(ip-1, breakLine, instruction);
if(!breakLine)
goto breakContinue;
TelDebugger->executionStopped(this, breakLine);
// Notify the remote debugger.
if ( pRemoteDebugger != NULL )
pRemoteDebugger->executionStopped(this, breakLine);
goto breakContinue;
}
case OP_INVALID:
default:
// error!
goto execFinished;
}
}
execFinished:
if ( telDebuggerOn && setFrame < 0 )
TelDebugger->popStackFrame();
// Notify the remote debugger.
if ( pRemoteDebugger != NULL && setFrame < 0 )
pRemoteDebugger->popStackFrame();
if ( popFrame )
gEvalState.popFrame();
if(argv)
{
if(gEvalState.traceOn)
{
traceBuffer[0] = 0;
dStrcat(traceBuffer, "Leaving ");
if(packageName)
{
dStrcat(traceBuffer, "[");
dStrcat(traceBuffer, packageName);
dStrcat(traceBuffer, "]");
}
if(thisNamespace && thisNamespace->mName)
{
dSprintf(traceBuffer + dStrlen(traceBuffer), sizeof(traceBuffer) - dStrlen(traceBuffer),
"%s::%s() - return %s", thisNamespace->mName, thisFunctionName, STR.getStringValue());
}
else
{
dSprintf(traceBuffer + dStrlen(traceBuffer), sizeof(traceBuffer) - dStrlen(traceBuffer),
"%s() - return %s", thisFunctionName, STR.getStringValue());
}
Con::printf("%s", traceBuffer);
}
}
else
{
delete[] const_cast<char*>(globalStrings);
delete[] globalFloats;
globalStrings = NULL;
globalFloats = NULL;
}
mScriptEngine->smCurrentCodeBlock = saveCodeBlock;
if(saveCodeBlock && saveCodeBlock->name)
{
mScriptEngine->gCurrentFile = saveCodeBlock->name;
mScriptEngine->gCurrentRoot = saveCodeBlock->mRoot;
}
decRefCount();
#ifdef TORQUE_DEBUG
AssertFatal(!(STR.mStartStackSize > stackStart), "String stack not popped enough in script exec");
AssertFatal(!(STR.mStartStackSize < stackStart), "String stack popped too much in script exec");
#endif
return STR.getStringValue();
}
//------------------------------------------------------------
TSScriptEngine::TSScriptEngine()
{
}
TSScriptEngine::~TSScriptEngine()
{
}
// register class
void TSScriptEngine::registerClass(AbstractClassRep *rep)
{
}
// register namespace functions
void TSScriptEngine::registerNamespace(Namespace *ns)
{
}
// register object instance
void TSScriptEngine::registerObject(SimObject *object)
{
}
// unregister object instance
void TSScriptEngine::removeObject(SimObject *object)
{
}
// gets current stack
ScriptStack *TSScriptEngine::getStack()
{
}
ScriptStackValueRef TSScriptEngine::execute(S32 argc, ScriptStackValueRef argv[])
{
}
ScriptStackValueRef TSScriptEngine::executeOnObject(ScriptStackValueRef obj, S32 argc, ScriptStackValueRef argv[])
{
}
/// Evaluate an arbitrary chunk of code.
///
/// @param string Buffer containing code to execute.
/// @param echo Should we echo the string to the console?
/// @param fileName Indicate what file this code is coming from; used in error reporting and such.
ScriptStackValueRef TSScriptEngine::evaluate(const char* string, bool echo = false, const char *fileName = NULL)
{
}
/// Evaluate an arbitrary line of script.
///
/// This wraps dVsprintf(), so you can substitute parameters into the code being executed.
ScriptStackValueRef TSScriptEngine::evaluatef(const char* string, ...)
{
}
// Compiles a file to a binary script
bool TSScriptEngine::compileFile(const char *filename)
{
}
// Executes a file to a binary script
bool TSScriptEngine::executeFile(const char *filename)
{
}
//-------------------------------------------------------------------------
StringTableEntry TSScriptEngine::getCurrentCodeBlockName()
{
if (getCurrentBlock())
return getCurrentBlock()->name;
else
return NULL;
}
StringTableEntry TSScriptEngine::getCurrentCodeBlockFullPath()
{
if (getCurrentBlock())
return getCurrentBlock()->fullPath;
else
return NULL;
}
StringTableEntry TSScriptEngine::getCurrentCodeBlockModName()
{
if (getCurrentBlock())
return getCurrentBlock()->modPath;
else
return NULL;
}
CodeBlock *TSScriptEngine::find(StringTableEntry name)
{
for(CodeBlock *walk = getCodeBlockList(); walk; walk = walk->nextFile)
if(walk->name == name)
return walk;
return NULL;
}
void TSScriptEngine::incCodeBlockRef(void *code)
{
CodeBlock *block = dynamic_cast<CodeBlock*>(code);
if (block)
{
block->incRefCount();
}
}
void TSScriptEngine::decCodeBlockRef(void *code)
{
CodeBlock *block = dynamic_cast<CodeBlock*>(code);
if (block)
{
block->decRefCount();
}
}
void TSScriptEngine::getFunctionArgs(char buffer[1024], void* code, U32 offset)
{
CodeBlock *block = dynamic_cast<CodeBlock*>(code);
if (block)
{
block->getFunctionArgs(buffer, offset);
}
}
const char *TSScriptEngine::execCodeBlock(void* code, U32 offset, const char *functionName, Namespace *thisNamespace, U32 argc, ScriptStackValueRef argv[], bool noCalls, StringTableEntry packageName)
{
CodeBlock *block = dynamic_cast<CodeBlock*>(code);
if (block)
{
// TODO block->decRefCount();
}
}
//
// TSScriptEngine.h
// Torque2D
//
// Created by James Urquhart on 29/10/2014.
// Copyright (c) 2014 Michael Perry. All rights reserved.
//
#ifndef __Torque2D__TSScriptEngine__
#define __Torque2D__TSScriptEngine__
#include "script/ScriptEngine.h"
#include "console/consoleInternal.h"
#include "string/stringStack.h"
/*
TODOS
Refer to current ScriptEngine using "script" parameter in ConsoleFUnction etc
Remove Codeblock dependence from other files
Find better way of returning values from console functions
FINAL STUFF
Move TS specific stuff from console
*/
class TSScriptEngine
{
public:
enum EvalConstants {
MaxStackSize = 1024,
MethodOnComponent = -2
};
TSScriptEngine();
virtual ~TSScriptEngine();
ExprEvalState gEvalState;
StringStack STR;
U32 FLT;
U32 UINT;
F64 floatStack[MaxStackSize];
S64 intStack[MaxStackSize];
StringTableEntry gCurrentFile;
StringTableEntry gCurrentRoot;
static ScriptEngine *getInstance();
// register class
virtual void registerClass(AbstractClassRep *rep);
// register namespace functions
virtual void registerNamespace(Namespace *ns);
// register object instance
virtual void registerObject(SimObject *object);
// unregister object instance
virtual void removeObject(SimObject *object);
// gets current stack
virtual ScriptStack *getStack();
virtual ScriptStackValueRef execute(S32 argc, ScriptStackValueRef argv[]);
virtual ScriptStackValueRef executeOnObject(ScriptStackValueRef obj, S32 argc, ScriptStackValueRef argv[]);
/// Evaluate an arbitrary chunk of code.
///
/// @param string Buffer containing code to execute.
/// @param echo Should we echo the string to the console?
/// @param fileName Indicate what file this code is coming from; used in error reporting and such.
virtual ScriptStackValueRef evaluate(const char* string, bool echo = false, const char *fileName = NULL);
/// Evaluate an arbitrary line of script.
///
/// This wraps dVsprintf(), so you can substitute parameters into the code being executed.
virtual ScriptStackValueRef evaluatef(const char* string, ...);
// Compiles a file to a binary script
virtual bool compileFile(const char *filename) = 0;
// Executes a file to a binary script
virtual bool executeFile(const char *filename) = 0;
// TS Specific helpers
void setFieldComponent( SimObject* object, StringTableEntry field, const char* array, StringTableEntry subField );
inline ExprEvalState *getEvalState() { return &gEvalState; }
// Stuff from CodeBlock
public:
CodeBlock* smCodeBlockList;
CodeBlock* smCurrentCodeBlock;
public:
CodeBlock* getCurrentBlock()
{
return smCurrentCodeBlock;
}
CodeBlock *getCodeBlockList()
{
return smCodeBlockList;
}
StringTableEntry getCurrentCodeBlockName();
StringTableEntry getCurrentCodeBlockFullPath();
StringTableEntry getCurrentCodeBlockModName();
CodeBlock *findCodeblock(StringTableEntry);
virtual void incCodeBlockRef(void *code);
virtual void decCodeBlockRef(void *code);
virtual void getFunctionArgs(char buffer[1024], void* code, U32 offset);
virtual const char *execCodeBlock(void* code, U32 offset, const char *functionName, Namespace *thisNamespace, U32 argc, ScriptStackValueRef argv[], bool noCalls, StringTableEntry packageName) { return ""; }
};
#endif /* defined(__Torque2D__TSScriptEngine__) */
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment