namespace LuaInterface { using System; using System.IO; using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; using System.Reflection; using System.Threading; using OpenRA; using System.Text; /* * 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 { /*readonly */ IntPtr luaState; ObjectTranslator translator; LuaFunctionCallback panicCallback; LuaCSFunction tracebackFunction; // lockCallback, unlockCallback; used by debug code commented out for now public Lua() { luaState = LuaDLL.luaL_newstate(); // steffenj: Lua 5.1.1 API change (lua_open is gone) // Load libraries LuaDLL.luaL_openlibs(luaState); // Add LuaInterface marker LuaDLL.lua_pushstring(luaState, "LUAINTERFACE LOADED"); LuaDLL.lua_pushboolean(luaState, true); LuaDLL.lua_settable(luaState, (int) LuaIndexes.LUA_REGISTRYINDEX); translator=new ObjectTranslator(this,luaState); tracebackFunction = new LuaCSFunction(traceback); // We need to keep this in a managed reference so the delegate doesn't get garbage collected panicCallback = new LuaFunctionCallback(PanicCallback); LuaDLL.lua_atpanic(luaState, panicCallback); } private bool _StatePassed; /* * CAUTION: LuaInterface.Lua instances can't share the same lua state! */ public Lua( Int64 luaState ) { // Check for existing LuaInterface marker 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 { // Add LuaInterface marker 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); translator = new ObjectTranslator(this, this.luaState); } _StatePassed = true; } public void Close() { if (_StatePassed) return; if (luaState != IntPtr.Zero) LuaDLL.lua_close(luaState); //luaState = IntPtr.Zero; <- suggested by Christopher Cebulski http://luaforge.net/forum/forum.php?thread_id=44593&forum_id=146 } 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 /// /// Thrown if the script caused an exception void ThrowExceptionFromError(int oldTop) { object err = translator.getObject(luaState, -1); LuaDLL.lua_settop(luaState, oldTop); // A pre-wrapped exception - just rethrow it (stack trace of InnerException will be preserved) LuaScriptException luaEx = err as LuaScriptException; if (luaEx != null) throw luaEx; // A non-wrapped Lua error (best interpreted as a string) - wrap it and throw it if (err == null) err = "Unknown Lua Error"; throw new LuaScriptException(err.ToString(), ""); } /// /// 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; } private bool executing; /// /// True while a script is being executed /// public bool IsExecuting { get { return executing; } } /// /// /// /// /// /// public LuaFunction LoadString(string chunk, string name) { int oldTop = LuaDLL.lua_gettop(luaState); executing = true; // Somehow, on OS X, we need to use the UTF-8 byte count rather than the string length // Adapted for OpenRA var chunkLength = Platform.CurrentPlatform != PlatformType.Windows ? Encoding.UTF8.GetByteCount(chunk) : chunk.Length; try { if (LuaDLL.luaL_loadbuffer(luaState, chunk, chunkLength, name) != 0) ThrowExceptionFromError(oldTop); } finally { executing = false; } LuaFunction result = translator.getFunction(luaState, -1); translator.popValues(luaState, oldTop); return result; } /// /// /// /// /// public LuaFunction LoadFile(string fileName) { int oldTop = LuaDLL.lua_gettop(luaState); if (LuaDLL.luaL_loadfile(luaState, fileName) != 0) ThrowExceptionFromError(oldTop); LuaFunction result = translator.getFunction(luaState, -1); translator.popValues(luaState, oldTop); return result; } /* * Excutes a Lua chunk and returns all the chunk's return * values in an array */ public object[] DoString(string chunk) { return DoString(chunk,"chunk"); } /// /// Executes a Lua chnk and returns all the chunk's return values in an array. /// /// Chunk to execute /// Name to associate with the chunk /// public object[] DoString(string chunk, string chunkName) { int oldTop = LuaDLL.lua_gettop(luaState); executing = true; // Somehow, on OS X, we need to use the UTF-8 byte count rather than the string length // Adapted for OpenRA var chunkLength = Platform.CurrentPlatform != PlatformType.Windows ? Encoding.UTF8.GetByteCount(chunk) : chunk.Length; if (LuaDLL.luaL_loadbuffer(luaState, chunk, chunkLength, chunkName) == 0) { try { if (LuaDLL.lua_pcall(luaState, 0, -1, 0) == 0) return translator.popValues(luaState, oldTop); else ThrowExceptionFromError(oldTop); } finally { executing = false; } } else ThrowExceptionFromError(oldTop); return null; // Never reached - keeps compiler happy } private int traceback(IntPtr luaState) { LuaDLL.lua_getglobal(luaState,"debug"); LuaDLL.lua_getfield(luaState,-1,"traceback"); LuaDLL.lua_pushvalue(luaState,1); LuaDLL.lua_pushnumber(luaState,2); LuaDLL.lua_call (luaState,2,1); return 1; } /* * Excutes a Lua file and returns all the chunk's return * values in an array */ public object[] DoFile(string fileName) { LuaDLL.lua_pushstdcallcfunction(luaState,tracebackFunction); int oldTop=LuaDLL.lua_gettop(luaState); if(LuaDLL.luaL_loadfile(luaState,fileName)==0) { executing = true; try { if (LuaDLL.lua_pcall(luaState, 0, -1, -2) == 0) return translator.popValues(luaState, oldTop); else ThrowExceptionFromError(oldTop); } finally { executing = false; } } else ThrowExceptionFromError(oldTop); return null; // Never reached - keeps compiler happy } public void CollectGarbage() { LuaDLL.lua_gc( luaState, LuaGCOptions.LUA_GCCOLLECT, 0 ); } /* * 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); // Globals auto-complete if (value == null) { // Remove now obsolete entries globals.Remove(fullPath); } else { // Add new entries if (!globals.Contains(fullPath)) registerGlobal(fullPath, value.GetType(), 0); } } } #region Globals auto-complete private readonly List globals = new List(); private bool globalsSorted; /// /// An alphabetically sorted list of all globals (objects, methods, etc.) externally added to this Lua instance /// /// Members of globals are also listed. The formatting is optimized for text input auto-completion. public IEnumerable Globals { get { // Only sort list when necessary if (!globalsSorted) { globals.Sort(); globalsSorted = true; } return globals; } } /// /// Adds an entry to (recursivley handles 2 levels of members) /// /// The index accessor path ot the entry /// The type of the entry /// How deep have we gone with recursion? private void registerGlobal(string path, Type type, int recursionCounter) { // If the type is a global method, list it directly if (type == typeof(LuaCSFunction)) { // Format for easy method invocation globals.Add(path + "("); } // If the type is a class or an interface and recursion hasn't been running too long, list the members else if ((type.IsClass || type.IsInterface) && type != typeof(string) && recursionCounter < 2) { #region Methods foreach (MethodInfo method in type.GetMethods(BindingFlags.Public | BindingFlags.Instance)) { if ( // Check that the LuaHideAttribute and LuaGlobalAttribute were not applied (method.GetCustomAttributes(typeof(LuaHideAttribute), false).Length == 0) && (method.GetCustomAttributes(typeof(LuaGlobalAttribute), false).Length == 0) && // Exclude some generic .NET methods that wouldn't be very usefull in Lua method.Name != "GetType" && method.Name != "GetHashCode" && method.Name != "Equals" && method.Name != "ToString" && method.Name != "Clone" && method.Name != "Dispose" && method.Name != "GetEnumerator" && method.Name != "CopyTo" && !method.Name.StartsWith("get_", StringComparison.Ordinal) && !method.Name.StartsWith("set_", StringComparison.Ordinal) && !method.Name.StartsWith("add_", StringComparison.Ordinal) && !method.Name.StartsWith("remove_", StringComparison.Ordinal)) { // Format for easy method invocation string command = path + ":" + method.Name + "("; if (method.GetParameters().Length == 0) command += ")"; globals.Add(command); } } #endregion #region Fields foreach (FieldInfo field in type.GetFields(BindingFlags.Public | BindingFlags.Instance)) { if ( // Check that the LuaHideAttribute and LuaGlobalAttribute were not applied (field.GetCustomAttributes(typeof(LuaHideAttribute), false).Length == 0) && (field.GetCustomAttributes(typeof(LuaGlobalAttribute), false).Length == 0)) { // Go into recursion for members registerGlobal(path + "." + field.Name, field.FieldType, recursionCounter + 1); } } #endregion #region Properties foreach (PropertyInfo property in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)) { if ( // Check that the LuaHideAttribute and LuaGlobalAttribute were not applied (property.GetCustomAttributes(typeof(LuaHideAttribute), false).Length == 0) && (property.GetCustomAttributes(typeof(LuaGlobalAttribute), false).Length == 0) // Exclude some generic .NET properties that wouldn't be very usefull in Lua && property.Name != "Item") { // Go into recursion for members registerGlobal(path + "." + property.Name, property.PropertyType, recursionCounter + 1); } } #endregion } // Otherwise simply add the element to the list else globals.Add(path); // List will need to be sorted on next access globalsSorted = false; } #endregion /* * 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