/*
* This file is part of LuaInterface.
*
* Copyright (C) 2003-2005 Fabio Mascarenhas de Queiroz.
* Copyright (C) 2012 Megax
*
* 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.
*/
using System;
using System.IO;
using System.Reflection;
using System.Diagnostics;
using System.Collections;
using System.Collections.Generic;
using LuaInterface.Exceptions;
namespace LuaInterface
{
/*
* Passes objects from the CLR to Lua and vice-versa
*
* Author: Fabio Mascarenhas
* Version: 1.0
*/
public class ObjectTranslator
{
internal CheckType typeChecker;
// object # to object (FIXME - it should be possible to get object address as an object #)
public readonly Dictionary objects = new Dictionary();
// object to object #
public readonly Dictionary objectsBackMap = new Dictionary();
internal Lua interpreter;
private MetaFunctions metaFunctions;
private List assemblies;
private KopiLua.Lua.lua_CFunction registerTableFunction,unregisterTableFunction,getMethodSigFunction,
getConstructorSigFunction,importTypeFunction,loadAssemblyFunction;
internal EventHandlerContainer pendingEvents = new EventHandlerContainer();
public ObjectTranslator(Lua interpreter,KopiLua.Lua.lua_State luaState)
{
this.interpreter=interpreter;
typeChecker=new CheckType(this);
metaFunctions=new MetaFunctions(this);
assemblies=new List();
importTypeFunction=new KopiLua.Lua.lua_CFunction(this.importType);
loadAssemblyFunction=new KopiLua.Lua.lua_CFunction(this.loadAssembly);
registerTableFunction=new KopiLua.Lua.lua_CFunction(this.registerTable);
unregisterTableFunction=new KopiLua.Lua.lua_CFunction(this.unregisterTable);
getMethodSigFunction=new KopiLua.Lua.lua_CFunction(this.getMethodSignature);
getConstructorSigFunction=new KopiLua.Lua.lua_CFunction(this.getConstructorSignature);
createLuaObjectList(luaState);
createIndexingMetaFunction(luaState);
createBaseClassMetatable(luaState);
createClassMetatable(luaState);
createFunctionMetatable(luaState);
setGlobalFunctions(luaState);
}
/*
* Sets up the list of objects in the Lua side
*/
private void createLuaObjectList(KopiLua.Lua.lua_State luaState)
{
KopiLua.Lua.lua_pushstring(luaState,"luaNet_objects");
KopiLua.Lua.lua_newtable(luaState);
KopiLua.Lua.lua_newtable(luaState);
KopiLua.Lua.lua_pushstring(luaState,"__mode");
KopiLua.Lua.lua_pushstring(luaState,"v");
KopiLua.Lua.lua_settable(luaState,-3);
KopiLua.Lua.lua_setmetatable(luaState,-2);
KopiLua.Lua.lua_settable(luaState, (int) PseudoIndex.Registry);
}
/*
* Registers the indexing function of CLR objects
* passed to Lua
*/
private void createIndexingMetaFunction(KopiLua.Lua.lua_State luaState)
{
KopiLua.Lua.lua_pushstring(luaState,"luaNet_indexfunction");
LuaLib.luaL_dostring(luaState,MetaFunctions.luaIndexFunction); // steffenj: lua_dostring renamed to luaL_dostring
//LuaLib.lua_pushstdcallcfunction(luaState,indexFunction);
KopiLua.Lua.lua_rawset(luaState, (int) PseudoIndex.Registry);
}
/*
* Creates the metatable for superclasses (the base
* field of registered tables)
*/
private void createBaseClassMetatable(KopiLua.Lua.lua_State luaState)
{
KopiLua.Lua.luaL_newmetatable(luaState,"luaNet_searchbase");
KopiLua.Lua.lua_pushstring(luaState,"__gc");
LuaLib.lua_pushstdcallcfunction(luaState,metaFunctions.gcFunction);
KopiLua.Lua.lua_settable(luaState,-3);
KopiLua.Lua.lua_pushstring(luaState,"__tostring");
LuaLib.lua_pushstdcallcfunction(luaState,metaFunctions.toStringFunction);
KopiLua.Lua.lua_settable(luaState,-3);
KopiLua.Lua.lua_pushstring(luaState,"__index");
LuaLib.lua_pushstdcallcfunction(luaState,metaFunctions.baseIndexFunction);
KopiLua.Lua.lua_settable(luaState,-3);
KopiLua.Lua.lua_pushstring(luaState,"__newindex");
LuaLib.lua_pushstdcallcfunction(luaState,metaFunctions.newindexFunction);
KopiLua.Lua.lua_settable(luaState,-3);
KopiLua.Lua.lua_settop(luaState,-2);
}
/*
* Creates the metatable for type references
*/
private void createClassMetatable(KopiLua.Lua.lua_State luaState)
{
KopiLua.Lua.luaL_newmetatable(luaState,"luaNet_class");
KopiLua.Lua.lua_pushstring(luaState,"__gc");
LuaLib.lua_pushstdcallcfunction(luaState,metaFunctions.gcFunction);
KopiLua.Lua.lua_settable(luaState,-3);
KopiLua.Lua.lua_pushstring(luaState,"__tostring");
LuaLib.lua_pushstdcallcfunction(luaState,metaFunctions.toStringFunction);
KopiLua.Lua.lua_settable(luaState,-3);
KopiLua.Lua.lua_pushstring(luaState,"__index");
LuaLib.lua_pushstdcallcfunction(luaState,metaFunctions.classIndexFunction);
KopiLua.Lua.lua_settable(luaState,-3);
KopiLua.Lua.lua_pushstring(luaState,"__newindex");
LuaLib.lua_pushstdcallcfunction(luaState,metaFunctions.classNewindexFunction);
KopiLua.Lua.lua_settable(luaState,-3);
KopiLua.Lua.lua_pushstring(luaState,"__call");
LuaLib.lua_pushstdcallcfunction(luaState,metaFunctions.callConstructorFunction);
KopiLua.Lua.lua_settable(luaState,-3);
KopiLua.Lua.lua_settop(luaState,-2);
}
/*
* Registers the global functions used by LuaInterface
*/
private void setGlobalFunctions(KopiLua.Lua.lua_State luaState)
{
LuaLib.lua_pushstdcallcfunction(luaState,metaFunctions.indexFunction);
KopiLua.Lua.lua_setglobal(luaState,"get_object_member");
LuaLib.lua_pushstdcallcfunction(luaState,importTypeFunction);
KopiLua.Lua.lua_setglobal(luaState,"import_type");
LuaLib.lua_pushstdcallcfunction(luaState,loadAssemblyFunction);
KopiLua.Lua.lua_setglobal(luaState,"load_assembly");
LuaLib.lua_pushstdcallcfunction(luaState,registerTableFunction);
KopiLua.Lua.lua_setglobal(luaState,"make_object");
LuaLib.lua_pushstdcallcfunction(luaState,unregisterTableFunction);
KopiLua.Lua.lua_setglobal(luaState,"free_object");
LuaLib.lua_pushstdcallcfunction(luaState,getMethodSigFunction);
KopiLua.Lua.lua_setglobal(luaState,"get_method_bysig");
LuaLib.lua_pushstdcallcfunction(luaState,getConstructorSigFunction);
KopiLua.Lua.lua_setglobal(luaState,"get_constructor_bysig");
}
/*
* Creates the metatable for delegates
*/
private void createFunctionMetatable(KopiLua.Lua.lua_State luaState)
{
KopiLua.Lua.luaL_newmetatable(luaState,"luaNet_function");
KopiLua.Lua.lua_pushstring(luaState,"__gc");
LuaLib.lua_pushstdcallcfunction(luaState,metaFunctions.gcFunction);
KopiLua.Lua.lua_settable(luaState,-3);
KopiLua.Lua.lua_pushstring(luaState,"__call");
LuaLib.lua_pushstdcallcfunction(luaState,metaFunctions.execDelegateFunction);
KopiLua.Lua.lua_settable(luaState,-3);
KopiLua.Lua.lua_settop(luaState,-2);
}
/*
* Passes errors (argument e) to the Lua interpreter
*/
internal void throwError(KopiLua.Lua.lua_State luaState, object e)
{
// We use this to remove anything pushed by luaL_where
int oldTop = KopiLua.Lua.lua_gettop(luaState);
// Stack frame #1 is our C# wrapper, so not very interesting to the user
// Stack frame #2 must be the lua code that called us, so that's what we want to use
KopiLua.Lua.luaL_where(luaState, 1);
object[] curlev = popValues(luaState, oldTop);
// Determine the position in the script where the exception was triggered
string errLocation = "";
if (curlev.Length > 0)
errLocation = curlev[0].ToString();
string message = e as string;
if (message != null)
{
// Wrap Lua error (just a string) and store the error location
e = new LuaScriptException(message, errLocation);
}
else
{
Exception ex = e as Exception;
if (ex != null)
{
// Wrap generic .NET exception as an InnerException and store the error location
e = new LuaScriptException(ex, errLocation);
}
}
push(luaState, e);
KopiLua.Lua.lua_error(luaState);
}
/*
* Implementation of load_assembly. Throws an error
* if the assembly is not found.
*/
private int loadAssembly(KopiLua.Lua.lua_State luaState)
{
try
{
string assemblyName=KopiLua.Lua.lua_tostring(luaState,1).ToString();
Assembly assembly = null;
try
{
assembly = Assembly.Load(assemblyName);
}
catch (BadImageFormatException)
{
// The assemblyName was invalid. It is most likely a path.
}
if (assembly == null)
{
assembly = Assembly.Load(AssemblyName.GetAssemblyName(assemblyName));
}
if (assembly != null && !assemblies.Contains(assembly))
{
assemblies.Add(assembly);
}
}
catch(Exception e)
{
throwError(luaState,e);
}
return 0;
}
internal Type FindType(string className)
{
foreach(Assembly assembly in assemblies)
{
Type klass=assembly.GetType(className);
if(klass!=null)
{
return klass;
}
}
return null;
}
/*
* Implementation of import_type. Returns nil if the
* type is not found.
*/
private int importType(KopiLua.Lua.lua_State luaState)
{
string className=KopiLua.Lua.lua_tostring(luaState,1).ToString();
Type klass=FindType(className);
if(klass!=null)
pushType(luaState,klass);
else
KopiLua.Lua.lua_pushnil(luaState);
return 1;
}
/*
* Implementation of make_object. Registers a table (first
* argument in the stack) as an object subclassing the
* type passed as second argument in the stack.
*/
private int registerTable(KopiLua.Lua.lua_State luaState)
{
if(KopiLua.Lua.lua_type(luaState,1).ToLuaTypes()==LuaTypes.Table)
{
LuaTable luaTable=getTable(luaState,1);
string superclassName = KopiLua.Lua.lua_tostring(luaState, 2).ToString();
if (superclassName != null)
{
Type klass = FindType(superclassName);
if (klass != null)
{
// Creates and pushes the object in the stack, setting
// it as the metatable of the first argument
object obj = CodeGeneration.Instance.GetClassInstance(klass, luaTable);
pushObject(luaState, obj, "luaNet_metatable");
KopiLua.Lua.lua_newtable(luaState);
KopiLua.Lua.lua_pushstring(luaState, "__index");
KopiLua.Lua.lua_pushvalue(luaState, -3);
KopiLua.Lua.lua_settable(luaState, -3);
KopiLua.Lua.lua_pushstring(luaState, "__newindex");
KopiLua.Lua.lua_pushvalue(luaState, -3);
KopiLua.Lua.lua_settable(luaState, -3);
KopiLua.Lua.lua_setmetatable(luaState, 1);
// Pushes the object again, this time as the base field
// of the table and with the luaNet_searchbase metatable
KopiLua.Lua.lua_pushstring(luaState, "base");
int index = addObject(obj);
pushNewObject(luaState, obj, index, "luaNet_searchbase");
KopiLua.Lua.lua_rawset(luaState, 1);
}
else
throwError(luaState, "register_table: can not find superclass '" + superclassName + "'");
}
else
throwError(luaState, "register_table: superclass name can not be null");
}
else throwError(luaState,"register_table: first arg is not a table");
return 0;
}
/*
* Implementation of free_object. Clears the metatable and the
* base field, freeing the created object for garbage-collection
*/
private int unregisterTable(KopiLua.Lua.lua_State luaState)
{
try
{
if(KopiLua.Lua.lua_getmetatable(luaState,1)!=0)
{
KopiLua.Lua.lua_pushstring(luaState,"__index");
KopiLua.Lua.lua_gettable(luaState,-2);
object obj=getRawNetObject(luaState,-1);
if(obj==null) throwError(luaState,"unregister_table: arg is not valid table");
FieldInfo luaTableField=obj.GetType().GetField("__luaInterface_luaTable");
if(luaTableField==null) throwError(luaState,"unregister_table: arg is not valid table");
luaTableField.SetValue(obj,null);
KopiLua.Lua.lua_pushnil(luaState);
KopiLua.Lua.lua_setmetatable(luaState,1);
KopiLua.Lua.lua_pushstring(luaState,"base");
KopiLua.Lua.lua_pushnil(luaState);
KopiLua.Lua.lua_settable(luaState,1);
}
else throwError(luaState,"unregister_table: arg is not valid table");
}
catch(Exception e)
{
throwError(luaState,e.Message);
}
return 0;
}
/*
* Implementation of get_method_bysig. Returns nil
* if no matching method is not found.
*/
private int getMethodSignature(KopiLua.Lua.lua_State luaState)
{
IReflect klass; object target;
int udata=LuaLib.luanet_checkudata(luaState,1,"luaNet_class");
if(udata!=-1)
{
klass=(IReflect)objects[udata];
target=null;
}
else
{
target=getRawNetObject(luaState,1);
if(target==null)
{
throwError(luaState,"get_method_bysig: first arg is not type or object reference");
KopiLua.Lua.lua_pushnil(luaState);
return 1;
}
klass=target.GetType();
}
string methodName=KopiLua.Lua.lua_tostring(luaState,2).ToString();
Type[] signature=new Type[KopiLua.Lua.lua_gettop(luaState)-2];
for(int i=0;i
/// Given the Lua int ID for an object remove it from our maps
///
///
internal void collectObject(int udata)
{
object o;
bool found = objects.TryGetValue(udata, out o);
// The other variant of collectObject might have gotten here first, in that case we will silently ignore the missing entry
if (found)
{
// Debug.WriteLine("Removing " + o.ToString() + " @ " + udata);
objects.Remove(udata);
objectsBackMap.Remove(o);
}
}
///
/// Given an object reference, remove it from our maps
///
///
void collectObject(object o, int udata)
{
// Debug.WriteLine("Removing " + o.ToString() + " @ " + udata);
objects.Remove(udata);
objectsBackMap.Remove(o);
}
///
/// We want to ensure that objects always have a unique ID
///
int nextObj = 0;
int addObject(object obj)
{
// New object: inserts it in the list
int index = nextObj++;
// Debug.WriteLine("Adding " + obj.ToString() + " @ " + index);
objects[index] = obj;
objectsBackMap[obj] = index;
return index;
}
/*
* Gets an object from the Lua stack according to its Lua type.
*/
internal object getObject(KopiLua.Lua.lua_State luaState,int index)
{
LuaTypes type=KopiLua.Lua.lua_type(luaState,index).ToLuaTypes();
switch(type)
{
case LuaTypes.Number:
{
return KopiLua.Lua.lua_tonumber(luaState,index);
}
case LuaTypes.String:
{
return KopiLua.Lua.lua_tostring(luaState,index);
}
case LuaTypes.Boolean:
{
return KopiLua.Lua.lua_toboolean(luaState,index);
}
case LuaTypes.Table:
{
return getTable(luaState,index);
}
case LuaTypes.Function:
{
return getFunction(luaState,index);
}
case LuaTypes.UserData:
{
int udata=LuaLib.luanet_tonetobject(luaState,index);
Console.WriteLine(udata);
if(udata!=-1)
return objects[udata];
else
return getUserData(luaState,index);
}
default:
return null;
}
}
/*
* Gets the table in the index positon of the Lua stack.
*/
internal LuaTable getTable(KopiLua.Lua.lua_State luaState,int index)
{
KopiLua.Lua.lua_pushvalue(luaState,index);
return new LuaTable(LuaLib.lua_ref(luaState,1),interpreter);
}
/*
* Gets the userdata in the index positon of the Lua stack.
*/
internal LuaUserData getUserData(KopiLua.Lua.lua_State luaState,int index)
{
KopiLua.Lua.lua_pushvalue(luaState,index);
return new LuaUserData(LuaLib.lua_ref(luaState,1),interpreter);
}
/*
* Gets the function in the index positon of the Lua stack.
*/
internal LuaFunction getFunction(KopiLua.Lua.lua_State luaState,int index)
{
KopiLua.Lua.lua_pushvalue(luaState,index);
return new LuaFunction(LuaLib.lua_ref(luaState,1),interpreter);
}
/*
* Gets the CLR object in the index positon of the Lua stack. Returns
* delegates as Lua functions.
*/
internal object getNetObject(KopiLua.Lua.lua_State luaState,int index)
{
int idx=LuaLib.luanet_tonetobject(luaState,index);
if(idx!=-1)
return objects[idx];
else
return null;
}
/*
* Gets the CLR object in the index positon of the Lua stack. Returns
* delegates as is.
*/
internal object getRawNetObject(KopiLua.Lua.lua_State luaState,int index)
{
int udata=LuaLib.luanet_rawnetobj(luaState,index);
if(udata!=-1)
{
return objects[udata];
}
return null;
}
/*
* Pushes the entire array into the Lua stack and returns the number
* of elements pushed.
*/
internal int returnValues(KopiLua.Lua.lua_State luaState, object[] returnValues)
{
if(KopiLua.Lua.lua_checkstack(luaState,returnValues.Length+5).ToBoolean())
{
for(int i=0;i