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