namespace LuaInterface
{
using System;
using System.IO;
using System.Collections;
using System.Collections.Specialized;
using System.Reflection;
using System.Threading;
using Lua511;
/*
* Main class of LuaInterface
* Object-oriented wrapper to Lua API
*
* Author: Fabio Mascarenhas
* Version: 1.0
*
* // steffenj: important changes in Lua class:
* - removed all Open*Lib() functions
* - all libs automatically open in the Lua class constructor (just assign nil to unwanted libs)
* */
public class Lua : IDisposable
{
static string init_luanet =
"local metatable = {} \n"+
"local import_type = luanet.import_type \n"+
"local load_assembly = luanet.load_assembly \n"+
" \n"+
"-- Lookup a .NET identifier component. \n"+
"function metatable:__index(key) -- key is e.g. \"Form\" \n"+
" -- Get the fully-qualified name, e.g. \"System.Windows.Forms.Form\" \n"+
" local fqn = ((rawget(self,\".fqn\") and rawget(self,\".fqn\") .. \n"+
" \".\") or \"\") .. key \n"+
" \n"+
" -- Try to find either a luanet function or a CLR type \n"+
" local obj = rawget(luanet,key) or import_type(fqn) \n"+
" \n"+
" -- If key is neither a luanet function or a CLR type, then it is simply \n"+
" -- an identifier component. \n"+
" if obj == nil then \n"+
" -- It might be an assembly, so we load it too. \n"+
" load_assembly(fqn) \n"+
" obj = { [\".fqn\"] = fqn } \n"+
" setmetatable(obj, metatable) \n"+
" end \n"+
" \n"+
" -- Cache this lookup \n"+
" rawset(self, key, obj) \n"+
" return obj \n"+
"end \n"+
" \n"+
"-- A non-type has been called; e.g. foo = System.Foo() \n"+
"function metatable:__call(...) \n"+
" error(\"No such type: \" .. rawget(self,\".fqn\"), 2) \n"+
"end \n"+
" \n"+
"-- This is the root of the .NET namespace \n"+
"luanet[\".fqn\"] = false \n"+
"setmetatable(luanet, metatable) \n"+
" \n"+
"-- Preload the mscorlib assembly \n"+
"luanet.load_assembly(\"mscorlib\") \n";
readonly IntPtr luaState;
ObjectTranslator translator;
LuaCSFunction panicCallback, lockCallback, unlockCallback;
///
/// Used to ensure multiple .net threads all get serialized by this single lock for access to the lua stack/objects
///
object luaLock = new object();
public Lua()
{
luaState = LuaDLL.luaL_newstate(); // steffenj: Lua 5.1.1 API change (lua_open is gone)
//LuaDLL.luaopen_base(luaState); // steffenj: luaopen_* no longer used
LuaDLL.luaL_openlibs(luaState); // steffenj: Lua 5.1.1 API change (luaopen_base is gone, just open all libs right here)
LuaDLL.lua_pushstring(luaState, "LUAINTERFACE LOADED");
LuaDLL.lua_pushboolean(luaState, true);
LuaDLL.lua_settable(luaState, (int) LuaIndexes.LUA_REGISTRYINDEX);
LuaDLL.lua_newtable(luaState);
LuaDLL.lua_setglobal(luaState, "luanet");
LuaDLL.lua_pushvalue(luaState, (int)LuaIndexes.LUA_GLOBALSINDEX);
LuaDLL.lua_getglobal(luaState, "luanet");
LuaDLL.lua_pushstring(luaState, "getmetatable");
LuaDLL.lua_getglobal(luaState, "getmetatable");
LuaDLL.lua_settable(luaState, -3);
LuaDLL.lua_replace(luaState, (int)LuaIndexes.LUA_GLOBALSINDEX);
translator=new ObjectTranslator(this,luaState);
LuaDLL.lua_replace(luaState, (int)LuaIndexes.LUA_GLOBALSINDEX);
LuaDLL.luaL_dostring(luaState, Lua.init_luanet); // steffenj: lua_dostring renamed to luaL_dostring
// We need to keep this in a managed reference so the delegate doesn't get garbage collected
panicCallback = new LuaCSFunction(PanicCallback);
LuaDLL.lua_atpanic(luaState, panicCallback);
// LuaDLL.lua_atlock(luaState, lockCallback = new LuaCSFunction(LockCallback));
// LuaDLL.lua_atunlock(luaState, unlockCallback = new LuaCSFunction(UnlockCallback));
}
/*
* CAUTION: LuaInterface.Lua instances can't share the same lua state!
*/
public Lua(Int64 luaState)
{
IntPtr lState = new IntPtr(luaState);
LuaDLL.lua_pushstring(lState, "LUAINTERFACE LOADED");
LuaDLL.lua_gettable(lState, (int)LuaIndexes.LUA_REGISTRYINDEX);
if(LuaDLL.lua_toboolean(lState,-1)) {
LuaDLL.lua_settop(lState,-2);
throw new LuaException("There is already a LuaInterface.Lua instance associated with this Lua state");
} else {
LuaDLL.lua_settop(lState,-2);
LuaDLL.lua_pushstring(lState, "LUAINTERFACE LOADED");
LuaDLL.lua_pushboolean(lState, true);
LuaDLL.lua_settable(lState, (int)LuaIndexes.LUA_REGISTRYINDEX);
this.luaState=lState;
LuaDLL.lua_pushvalue(lState, (int)LuaIndexes.LUA_GLOBALSINDEX);
LuaDLL.lua_getglobal(lState, "luanet");
LuaDLL.lua_pushstring(lState, "getmetatable");
LuaDLL.lua_getglobal(lState, "getmetatable");
LuaDLL.lua_settable(lState, -3);
LuaDLL.lua_replace(lState, (int)LuaIndexes.LUA_GLOBALSINDEX);
translator=new ObjectTranslator(this, this.luaState);
LuaDLL.lua_replace(lState, (int)LuaIndexes.LUA_GLOBALSINDEX);
LuaDLL.luaL_dostring(lState, Lua.init_luanet); // steffenj: lua_dostring renamed to luaL_dostring
}
}
///
/// Called for each lua_lock call
///
///
/// Not yet used
int LockCallback(IntPtr luaState)
{
// Monitor.Enter(luaLock);
return 0;
}
///
/// Called for each lua_unlock call
///
///
/// Not yet used
int UnlockCallback(IntPtr luaState)
{
// Monitor.Exit(luaLock);
return 0;
}
static int PanicCallback(IntPtr luaState)
{
// string desc = LuaDLL.lua_tostring(luaState, 1);
string reason = String.Format("unprotected error in call to Lua API ({0})", LuaDLL.lua_tostring(luaState, -1));
// lua_tostring(L, -1);
throw new LuaException(reason);
}
///
/// Assuming we have a Lua error string sitting on the stack, throw a C# exception out to the user's app
///
void ThrowExceptionFromError(int oldTop)
{
object err = translator.getObject(luaState, -1);
LuaDLL.lua_settop(luaState, oldTop);
// If the 'error' on the stack is an actual C# exception, just rethrow it. Otherwise the value must have started
// as a true Lua error and is best interpreted as a string - wrap it in a LuaException and rethrow.
Exception thrown = err as Exception;
if (thrown == null)
{
if (err == null)
err = "Unknown Lua Error";
thrown = new LuaException(err.ToString());
}
throw thrown;
}
///
/// Convert C# exceptions into Lua errors
///
/// num of things on stack
/// null for no pending exception
internal int SetPendingException(Exception e)
{
Exception caughtExcept = e;
if (caughtExcept != null)
{
translator.throwError(luaState, caughtExcept);
LuaDLL.lua_pushnil(luaState);
return 1;
}
else
return 0;
}
/*
* Excutes a Lua chunk and returns all the chunk's return
* values in an array
*/
public object[] DoString(string chunk)
{
int oldTop=LuaDLL.lua_gettop(luaState);
if(LuaDLL.luaL_loadbuffer(luaState,chunk,"chunk")==0)
{
if (LuaDLL.lua_pcall(luaState, 0, -1, 0) == 0)
return translator.popValues(luaState, oldTop);
else
ThrowExceptionFromError(oldTop);
}
else
ThrowExceptionFromError(oldTop);
return null; // Never reached - keeps compiler happy
}
/*
* Excutes a Lua file and returns all the chunk's return
* values in an array
*/
public object[] DoFile(string fileName)
{
int oldTop=LuaDLL.lua_gettop(luaState);
if(LuaDLL.luaL_loadfile(luaState,fileName)==0)
{
if (LuaDLL.lua_pcall(luaState, 0, -1, 0) == 0)
return translator.popValues(luaState, oldTop);
else
ThrowExceptionFromError(oldTop);
}
else
ThrowExceptionFromError(oldTop);
return null; // Never reached - keeps compiler happy
}
/*
* Indexer for global variables from the LuaInterpreter
* Supports navigation of tables by using . operator
*/
public object this[string fullPath]
{
get
{
object returnValue=null;
int oldTop=LuaDLL.lua_gettop(luaState);
string[] path=fullPath.Split(new char[] { '.' });
LuaDLL.lua_getglobal(luaState,path[0]);
returnValue=translator.getObject(luaState,-1);
if(path.Length>1)
{
string[] remainingPath=new string[path.Length-1];
Array.Copy(path,1,remainingPath,0,path.Length-1);
returnValue=getObject(remainingPath);
}
LuaDLL.lua_settop(luaState,oldTop);
return returnValue;
}
set
{
int oldTop=LuaDLL.lua_gettop(luaState);
string[] path=fullPath.Split(new char[] { '.' });
if(path.Length==1)
{
translator.push(luaState,value);
LuaDLL.lua_setglobal(luaState,fullPath);
}
else
{
LuaDLL.lua_getglobal(luaState,path[0]);
string[] remainingPath=new string[path.Length-1];
Array.Copy(path,1,remainingPath,0,path.Length-1);
setObject(remainingPath,value);
}
LuaDLL.lua_settop(luaState,oldTop);
}
}
/*
* Navigates a table in the top of the stack, returning
* the value of the specified field
*/
internal object getObject(string[] remainingPath)
{
object returnValue=null;
for(int i=0;i