diff --git a/Assets/dll/KeraLua.dll b/Assets/dll/KeraLua.dll deleted file mode 100644 index e1aa1f948e..0000000000 Binary files a/Assets/dll/KeraLua.dll and /dev/null differ diff --git a/Assets/dll/NLua.dll b/Assets/dll/NLua.dll deleted file mode 100644 index ce3ea5a82b..0000000000 Binary files a/Assets/dll/NLua.dll and /dev/null differ diff --git a/Assets/dll/liblua54.so b/Assets/dll/liblua54.so deleted file mode 100644 index a1de693266..0000000000 Binary files a/Assets/dll/liblua54.so and /dev/null differ diff --git a/ExternalProjects/NLua/LICENSE b/ExternalProjects/NLua/LICENSE new file mode 100644 index 0000000000..a7ab4aaf71 --- /dev/null +++ b/ExternalProjects/NLua/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2021 Vinicius Jarina (viniciusjarina@gmail.com) + +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. \ No newline at end of file diff --git a/ExternalProjects/NLua/NLua.csproj b/ExternalProjects/NLua/NLua.csproj new file mode 100644 index 0000000000..01ebbb95d1 --- /dev/null +++ b/ExternalProjects/NLua/NLua.csproj @@ -0,0 +1,30 @@ + + + netstandard2.0 + NLua + NLua + false + + + + true + + + + + true + portable + true + + + + + + + + + + + disable + + \ No newline at end of file diff --git a/ExternalProjects/NLua/build_debug.sh b/ExternalProjects/NLua/build_debug.sh new file mode 100644 index 0000000000..c2127aded1 --- /dev/null +++ b/ExternalProjects/NLua/build_debug.sh @@ -0,0 +1 @@ +../.build_debug.sh \ No newline at end of file diff --git a/ExternalProjects/NLua/build_release.sh b/ExternalProjects/NLua/build_release.sh new file mode 100644 index 0000000000..801b8e5998 --- /dev/null +++ b/ExternalProjects/NLua/build_release.sh @@ -0,0 +1 @@ +../.build_release.sh \ No newline at end of file diff --git a/ExternalProjects/NLua/src/CheckType.cs b/ExternalProjects/NLua/src/CheckType.cs new file mode 100644 index 0000000000..84832514a5 --- /dev/null +++ b/ExternalProjects/NLua/src/CheckType.cs @@ -0,0 +1,414 @@ +using System; +using System.Collections.Generic; +using NLua.Method; +using NLua.Extensions; + +namespace NLua +{ + internal sealed class CheckType + { + internal readonly Dictionary _extractValues = new Dictionary(); + internal readonly ExtractValue _extractNetObject; + internal readonly ObjectTranslator _translator; + + public CheckType(ObjectTranslator translator) + { + _translator = translator; + _extractValues.Add(typeof(object), GetAsObject); + _extractValues.Add(typeof(sbyte), GetAsSbyte); + _extractValues.Add(typeof(byte), GetAsByte); + _extractValues.Add(typeof(short), GetAsShort); + _extractValues.Add(typeof(ushort), GetAsUshort); + _extractValues.Add(typeof(int), GetAsInt); + _extractValues.Add(typeof(uint), GetAsUint); + _extractValues.Add(typeof(long), GetAsLong); + _extractValues.Add(typeof(ulong), GetAsUlong); + _extractValues.Add(typeof(double), GetAsDouble); + _extractValues.Add(typeof(char), GetAsChar); + _extractValues.Add(typeof(float), GetAsFloat); + _extractValues.Add(typeof(decimal), GetAsDecimal); + _extractValues.Add(typeof(bool), GetAsBoolean); + _extractValues.Add(typeof(string), GetAsString); + _extractValues.Add(typeof(char[]), GetAsCharArray); + _extractValues.Add(typeof(byte[]), GetAsByteArray); + _extractValues.Add(typeof(LuaFunction), GetAsFunction); + _extractValues.Add(typeof(LuaTable), GetAsTable); + _extractValues.Add(typeof(LuaThread), GetAsThread); + _extractValues.Add(typeof(LuaUserData), GetAsUserdata); + _extractNetObject = GetAsNetObject; + } + + /// + /// Checks if the value at Lua stack index stackPos matches paramType, + /// returning a conversion function if it does and null otherwise. + /// + internal ExtractValue GetExtractor(ProxyType paramType) + { + return GetExtractor(paramType.UnderlyingSystemType); + } + + internal ExtractValue GetExtractor(Type paramType) + { + if (paramType.IsByRef) + paramType = paramType.GetElementType(); + + return _extractValues.ContainsKey(paramType) ? _extractValues[paramType] : _extractNetObject; + } + + internal ExtractValue CheckLuaType(LuaState luaState, int stackPos, Type paramType) + { + LuaType luatype = luaState.Type(stackPos); + + if (paramType.IsByRef) + paramType = paramType.GetElementType(); + + var underlyingType = Nullable.GetUnderlyingType(paramType); + + if (underlyingType != null) + { + paramType = underlyingType; // Silently convert nullable types to their non null requics + } + + + bool netParamIsNumeric = paramType == typeof(int) || + paramType == typeof(uint) || + paramType == typeof(long) || + paramType == typeof(ulong) || + paramType == typeof(short) || + paramType == typeof(ushort) || + paramType == typeof(float) || + paramType == typeof(double) || + paramType == typeof(decimal) || + paramType == typeof(byte) || + paramType == typeof(sbyte) || + paramType == typeof(char); + + // If it is a nullable + if (underlyingType != null) + { + // null can always be assigned to nullable + if (luatype == LuaType.Nil) + { + // Return the correct extractor anyways + if (netParamIsNumeric || paramType == typeof(bool)) + return _extractValues[paramType]; + return _extractNetObject; + } + } + + if (paramType == typeof(object)) + return _extractValues[paramType]; + + //CP: Added support for generic parameters + if (paramType.IsGenericParameter) + { + if (luatype == LuaType.Boolean) + return _extractValues[typeof(bool)]; + if (luatype == LuaType.String) + return _extractValues[typeof(string)]; + if (luatype == LuaType.Table) + return _extractValues[typeof(LuaTable)]; + if (luatype == LuaType.Thread) + return _extractValues[typeof(LuaThread)]; + if (luatype == LuaType.UserData) + return _extractValues[typeof(object)]; + if (luatype == LuaType.Function) + return _extractValues[typeof(LuaFunction)]; + if (luatype == LuaType.Number) + return _extractValues[typeof(double)]; + } + bool netParamIsString = paramType == typeof(string) || paramType == typeof(char[]) || paramType == typeof(byte[]); + + if (netParamIsNumeric) + { + if (luaState.IsNumericType(stackPos) && !netParamIsString) + return _extractValues[paramType]; + } + else if (paramType == typeof(bool)) + { + if (luaState.IsBoolean(stackPos)) + return _extractValues[paramType]; + } + else if (netParamIsString) + { + if (luaState.IsStringOrNumber(stackPos) || luatype == LuaType.Nil) + return _extractValues[paramType]; + } + else if (paramType == typeof(LuaTable)) + { + if (luatype == LuaType.Table || luatype == LuaType.Nil) + return _extractValues[paramType]; + } + else if (paramType == typeof(LuaThread)) + { + if (luatype == LuaType.Thread || luatype == LuaType.Nil) + return _extractValues[paramType]; + } + else if (paramType == typeof(LuaUserData)) + { + if (luatype == LuaType.UserData || luatype == LuaType.Nil) + return _extractValues[paramType]; + } + else if (paramType == typeof(LuaFunction)) + { + if (luatype == LuaType.Function || luatype == LuaType.Nil) + return _extractValues[paramType]; + } + else if (typeof(Delegate).IsAssignableFrom(paramType) && luatype == LuaType.Function && paramType.GetMethod("Invoke") != null) + return new DelegateGenerator(_translator, paramType).ExtractGenerated; + else if (paramType.IsInterface && luatype == LuaType.Table) + return new ClassGenerator(_translator, paramType).ExtractGenerated; + else if ((paramType.IsInterface || paramType.IsClass) && luatype == LuaType.Nil) + { + // kevinh - allow nil to be silently converted to null - extractNetObject will return null when the item ain't found + return _extractNetObject; + } + else if (luaState.Type(stackPos) == LuaType.Table) + { + if (luaState.GetMetaField(stackPos, "__index") != LuaType.Nil) + { + object obj = _translator.GetNetObject(luaState, -1); + luaState.SetTop(-2); + if (obj != null && paramType.IsInstanceOfType(obj)) + return _extractNetObject; + } + else + return null; + } + + object netObj = _translator.GetNetObject(luaState, stackPos); + if (netObj != null && paramType.IsInstanceOfType(netObj)) + return _extractNetObject; + + return null; + } + + /// + /// The following functions return the value in the Lua stack + /// index stackPos as the desired type if it can, or null + /// otherwise. + /// + private object GetAsSbyte(LuaState luaState, int stackPos) + { + if (!luaState.IsNumericType(stackPos)) + return null; + + if (luaState.IsInteger(stackPos)) + return (sbyte)luaState.ToInteger(stackPos); + + return (sbyte)luaState.ToNumber(stackPos); + } + + private object GetAsByte(LuaState luaState, int stackPos) + { + if (!luaState.IsNumericType(stackPos)) + return null; + + if (luaState.IsInteger(stackPos)) + return (byte)luaState.ToInteger(stackPos); + + return (byte)luaState.ToNumber(stackPos); + } + + private object GetAsShort(LuaState luaState, int stackPos) + { + if (!luaState.IsNumericType(stackPos)) + return null; + + if (luaState.IsInteger(stackPos)) + return (short)luaState.ToInteger(stackPos); + + return (short)luaState.ToNumber(stackPos); + } + + private object GetAsUshort(LuaState luaState, int stackPos) + { + if (!luaState.IsNumericType(stackPos)) + return null; + + if (luaState.IsInteger(stackPos)) + return (ushort)luaState.ToInteger(stackPos); + + return (ushort)luaState.ToNumber(stackPos); + } + + private object GetAsInt(LuaState luaState, int stackPos) + { + if (!luaState.IsNumericType(stackPos)) + return null; + + if (luaState.IsInteger(stackPos)) + return (int)luaState.ToInteger(stackPos); + + return (int)luaState.ToNumber(stackPos); + } + + private object GetAsUint(LuaState luaState, int stackPos) + { + if (!luaState.IsNumericType(stackPos)) + return null; + + if (luaState.IsInteger(stackPos)) + return (uint)luaState.ToInteger(stackPos); + + return (uint)luaState.ToNumber(stackPos); + } + + private object GetAsLong(LuaState luaState, int stackPos) + { + if (!luaState.IsNumericType(stackPos)) + return null; + + if (luaState.IsInteger(stackPos)) + return luaState.ToInteger(stackPos); + + return (long)luaState.ToNumber(stackPos); + } + + private object GetAsUlong(LuaState luaState, int stackPos) + { + if (!luaState.IsNumericType(stackPos)) + return null; + + if (luaState.IsInteger(stackPos)) + return (ulong)luaState.ToInteger(stackPos); + + return (ulong)luaState.ToNumber(stackPos); + } + + private object GetAsDouble(LuaState luaState, int stackPos) + { + if (!luaState.IsNumericType(stackPos)) + return null; + + if (luaState.IsInteger(stackPos)) + return (double)luaState.ToInteger(stackPos); + + return luaState.ToNumber(stackPos); + } + + private object GetAsChar(LuaState luaState, int stackPos) + { + if (!luaState.IsNumericType(stackPos)) + return null; + + if (luaState.IsInteger(stackPos)) + return (char)luaState.ToInteger(stackPos); + + return (char)luaState.ToNumber(stackPos); + } + + private object GetAsFloat(LuaState luaState, int stackPos) + { + if (!luaState.IsNumericType(stackPos)) + return null; + + if (luaState.IsInteger(stackPos)) + return (float)luaState.ToInteger(stackPos); + + return (float)luaState.ToNumber(stackPos); + } + + private object GetAsDecimal(LuaState luaState, int stackPos) + { + if (!luaState.IsNumericType(stackPos)) + return null; + + if (luaState.IsInteger(stackPos)) + return (decimal)luaState.ToInteger(stackPos); + + return (decimal)luaState.ToNumber(stackPos); + } + + private object GetAsBoolean(LuaState luaState, int stackPos) + { + return luaState.ToBoolean(stackPos); + } + + private object GetAsCharArray(LuaState luaState, int stackPos) + { + if (!luaState.IsStringOrNumber(stackPos)) + return null; + string retVal = luaState.ToString(stackPos, false); + return retVal.ToCharArray(); + } + + private object GetAsByteArray(LuaState luaState, int stackPos) + { + if (!luaState.IsStringOrNumber(stackPos)) + return null; + + byte [] retVal = luaState.ToBuffer(stackPos, false); + return retVal; + } + + private object GetAsString(LuaState luaState, int stackPos) + { + if (!luaState.IsStringOrNumber(stackPos)) + return null; + return luaState.ToString(stackPos, false); + } + + private object GetAsTable(LuaState luaState, int stackPos) + { + return _translator.GetTable(luaState, stackPos); + } + + private object GetAsThread(LuaState luaState, int stackPos) + { + return _translator.GetThread(luaState, stackPos); + } + + private object GetAsFunction(LuaState luaState, int stackPos) + { + return _translator.GetFunction(luaState, stackPos); + } + + private object GetAsUserdata(LuaState luaState, int stackPos) + { + return _translator.GetUserData(luaState, stackPos); + } + + public object GetAsObject(LuaState luaState, int stackPos) + { + if (luaState.Type(stackPos) == LuaType.Table) + { + if (luaState.GetMetaField(stackPos, "__index") != LuaType.Nil) + { + if (luaState.CheckMetaTable(-1, _translator.Tag)) + { + luaState.Insert(stackPos); + luaState.Remove(stackPos + 1); + } + else + luaState.SetTop(-2); + } + } + + object obj = _translator.GetObject(luaState, stackPos); + return obj; + } + + public object GetAsNetObject(LuaState luaState, int stackPos) + { + object obj = _translator.GetNetObject(luaState, stackPos); + + if (obj != null || luaState.Type(stackPos) != LuaType.Table) + return obj; + + if (luaState.GetMetaField(stackPos, "__index") == LuaType.Nil) + return null; + + if (luaState.CheckMetaTable(-1, _translator.Tag)) + { + luaState.Insert(stackPos); + luaState.Remove(stackPos + 1); + obj = _translator.GetNetObject(luaState, stackPos); + } + else + luaState.SetTop(-2); + + return obj; + } + } +} diff --git a/ExternalProjects/NLua/src/Event/DebugHookEventArgs.cs b/ExternalProjects/NLua/src/Event/DebugHookEventArgs.cs new file mode 100644 index 0000000000..ad8c36d14d --- /dev/null +++ b/ExternalProjects/NLua/src/Event/DebugHookEventArgs.cs @@ -0,0 +1,20 @@ +using System; + +namespace NLua.Event +{ + /// + /// Event args for hook callback event + /// + public class DebugHookEventArgs : EventArgs + { + public DebugHookEventArgs(LuaDebug luaDebug) + { + LuaDebug = luaDebug; + } + + /// + /// Lua Debug Information + /// + public LuaDebug LuaDebug { get; } + } +} diff --git a/ExternalProjects/NLua/src/Event/HookExceptionEventArgs.cs b/ExternalProjects/NLua/src/Event/HookExceptionEventArgs.cs new file mode 100644 index 0000000000..6224f53f00 --- /dev/null +++ b/ExternalProjects/NLua/src/Event/HookExceptionEventArgs.cs @@ -0,0 +1,14 @@ +using System; + +namespace NLua.Event +{ + public class HookExceptionEventArgs : EventArgs + { + public Exception Exception { get; } + + public HookExceptionEventArgs(Exception ex) + { + Exception = ex; + } + } +} diff --git a/ExternalProjects/NLua/src/Exceptions/LuaException.cs b/ExternalProjects/NLua/src/Exceptions/LuaException.cs new file mode 100644 index 0000000000..d8eef01092 --- /dev/null +++ b/ExternalProjects/NLua/src/Exceptions/LuaException.cs @@ -0,0 +1,20 @@ +using System; + +namespace NLua.Exceptions +{ + /// + /// Exceptions thrown by the Lua runtime + /// + [Serializable] + public class LuaException : Exception + { + public LuaException (string message) : base(message) + { + } + + public LuaException (string message, Exception innerException) : base(message, innerException) + { + } + + } +} diff --git a/ExternalProjects/NLua/src/Exceptions/LuaScriptException.cs b/ExternalProjects/NLua/src/Exceptions/LuaScriptException.cs new file mode 100644 index 0000000000..28a4440eca --- /dev/null +++ b/ExternalProjects/NLua/src/Exceptions/LuaScriptException.cs @@ -0,0 +1,52 @@ +using System; + +namespace NLua.Exceptions +{ + /// + /// Exceptions thrown by the Lua runtime because of errors in the script + /// + /// + [Serializable] + public class LuaScriptException : LuaException + { + /// + /// Returns true if the exception has occured as the result of a .NET exception in user code + /// + public bool IsNetException { get; } + + private readonly string _source; + + /// + /// The position in the script where the exception was triggered. + /// + public override string Source => _source; + + /// + /// Creates a new Lua-only exception. + /// + /// The message that describes the error. + /// The position in the script where the exception was triggered. + public LuaScriptException(string message, string source) : base(message) + { + _source = source; + } + + /// + /// Creates a new .NET wrapping exception. + /// + /// The .NET exception triggered by user-code. + /// The position in the script where the exception was triggered. + public LuaScriptException(Exception innerException, string source) + : base("A .NET exception occured in user-code", innerException) + { + _source = source; + IsNetException = true; + } + + public override string ToString() + { + // Prepend the error source + return GetType().FullName + ": " + _source + Message; + } + } +} diff --git a/ExternalProjects/NLua/src/Extensions/LuaExtensions.cs b/ExternalProjects/NLua/src/Extensions/LuaExtensions.cs new file mode 100644 index 0000000000..6774bb0c84 --- /dev/null +++ b/ExternalProjects/NLua/src/Extensions/LuaExtensions.cs @@ -0,0 +1,119 @@ +using System; +using System.Runtime.InteropServices; + +namespace NLua.Extensions +{ + internal static class LuaExtensions + { + public static bool CheckMetaTable(this LuaState state, int index, IntPtr tag) + { + if (!state.GetMetaTable(index)) + return false; + + state.PushLightUserData(tag); + state.RawGet(-2); + bool isNotNil = !state.IsNil(-1); + state.SetTop(-3); + return isNotNil; + } + + public static void PopGlobalTable(this LuaState luaState) + { + luaState.RawSetInteger(LuaRegistry.Index, (long) LuaRegistryIndex.Globals); + } + + public static void GetRef (this LuaState luaState, int reference) + { + luaState.RawGetInteger(LuaRegistry.Index, reference); + } + + // ReSharper disable once IdentifierTypo + public static void Unref (this LuaState luaState, int reference) + { + luaState.Unref(LuaRegistry.Index, reference); + } + + public static bool AreEqual(this LuaState luaState, int ref1, int ref2) + { + return luaState.Compare(ref1, ref2, LuaCompare.Equal); + } + + public static IntPtr CheckUData(this LuaState state, int ud, string name) + { + IntPtr p = state.ToUserData(ud); + if (p == IntPtr.Zero) + return IntPtr.Zero; + if (!state.GetMetaTable(ud)) + return IntPtr.Zero; + + state.GetField(LuaRegistry.Index, name); + + bool isEqual = state.RawEqual(-1, -2); + + state.Pop(2); + + if (isEqual) + return p; + + return IntPtr.Zero; + } + + public static int ToNetObject(this LuaState state, int index, IntPtr tag) + { + if (state.Type(index) != LuaType.UserData) + return -1; + + IntPtr userData; + + if (state.CheckMetaTable(index, tag)) + { + userData = state.ToUserData(index); + if (userData != IntPtr.Zero) + return Marshal.ReadInt32(userData); + } + + userData = state.CheckUData(index, "luaNet_class"); + if (userData != IntPtr.Zero) + return Marshal.ReadInt32(userData); + + userData = state.CheckUData(index, "luaNet_searchbase"); + if (userData != IntPtr.Zero) + return Marshal.ReadInt32(userData); + + userData = state.CheckUData(index, "luaNet_function"); + if (userData != IntPtr.Zero) + return Marshal.ReadInt32(userData); + + return -1; + } + + public static void NewUData(this LuaState state, int val) + { + IntPtr pointer = state.NewUserData(Marshal.SizeOf(typeof(int))); + Marshal.WriteInt32(pointer, val); + } + + public static int RawNetObj(this LuaState state, int index) + { + IntPtr pointer = state.ToUserData(index); + if (pointer == IntPtr.Zero) + return -1; + + return Marshal.ReadInt32(pointer); + } + + public static int CheckUObject(this LuaState state, int index, string name) + { + IntPtr udata = state.CheckUData(index, name); + if (udata == IntPtr.Zero) + return -1; + + return Marshal.ReadInt32(udata); + } + + public static bool IsNumericType(this LuaState state, int index) + { + return state.Type(index) == LuaType.Number; + } + } +} diff --git a/ExternalProjects/NLua/src/Extensions/StringExtensions.cs b/ExternalProjects/NLua/src/Extensions/StringExtensions.cs new file mode 100644 index 0000000000..b0c745fdc8 --- /dev/null +++ b/ExternalProjects/NLua/src/Extensions/StringExtensions.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; + +namespace NLua.Extensions +{ + internal static class StringExtensions + { + public static IEnumerable SplitWithEscape(this string input, char separator, char escapeCharacter) + { + int start = 0; + int index = 0; + while (index < input.Length) + { + index = input.IndexOf(separator, index); + if (index == -1) + break; + + if (input[index - 1] == escapeCharacter) + { + input = input.Remove(index - 1, 1); + continue; + } + + + yield return input.Substring(start, index - start); + index++; + start = index; + } + yield return input.Substring(start); + } + } +} diff --git a/ExternalProjects/NLua/src/Extensions/TypeExtensions.cs b/ExternalProjects/NLua/src/Extensions/TypeExtensions.cs new file mode 100644 index 0000000000..86c31efd3c --- /dev/null +++ b/ExternalProjects/NLua/src/Extensions/TypeExtensions.cs @@ -0,0 +1,142 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; + +namespace NLua.Extensions +{ + internal static class TypeExtensions + { + public static bool HasMethod(this Type t, string name) + { + var op = t.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static); + return op.Any(m => m.Name == name); + } + + public static bool HasAdditionOperator(this Type t) + { + if (t.IsPrimitive) + return true; + + return t.HasMethod("op_Addition"); + } + + public static bool HasSubtractionOperator(this Type t) + { + if (t.IsPrimitive) + return true; + + return t.HasMethod("op_Subtraction"); + } + + public static bool HasMultiplyOperator(this Type t) + { + if (t.IsPrimitive) + return true; + + return t.HasMethod("op_Multiply"); + } + + public static bool HasDivisionOperator(this Type t) + { + if (t.IsPrimitive) + return true; + + return t.HasMethod("op_Division"); + } + + public static bool HasModulusOperator(this Type t) + { + if (t.IsPrimitive) + return true; + + return t.HasMethod("op_Modulus"); + } + + public static bool HasUnaryNegationOperator(this Type t) + { + if (t.IsPrimitive) + return true; + // Unary - will always have only one version. + var op = t.GetMethod("op_UnaryNegation", BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static); + return op != null; + } + + public static bool HasEqualityOperator(this Type t) + { + if (t.IsPrimitive) + return true; + return t.HasMethod("op_Equality"); + } + + public static bool HasLessThanOperator(this Type t) + { + if (t.IsPrimitive) + return true; + + return t.HasMethod("op_LessThan"); + } + + public static bool HasLessThanOrEqualOperator(this Type t) + { + if (t.IsPrimitive) + return true; + return t.HasMethod("op_LessThanOrEqual"); + } + + public static MethodInfo[] GetMethods(this Type t, string name, BindingFlags flags) + { + return t.GetMethods(flags).Where(m => m.Name == name).ToArray(); + } + + public static MethodInfo[] GetExtensionMethods(this Type type, string name, IEnumerable assemblies = null) + { + var types = new List(); + + types.AddRange(type.Assembly.GetTypes().Where(t => t.IsPublic)); + + if (assemblies != null) + { + foreach (Assembly item in assemblies) + { + if (item == type.Assembly) + continue; + types.AddRange(item.GetTypes().Where(t => t.IsPublic && t.IsClass && t.IsSealed && t.IsAbstract && !t.IsNested)); + } + } + + // this thing is complex and not sure recommended changes are safe +#pragma warning disable MA0029 // Combine LINQ methods +#pragma warning disable BHI1002 // Do not use anonymous types (classes) + var query = types + .SelectMany(extensionType => extensionType.GetMethods(name, BindingFlags.Static | BindingFlags.Public), + (extensionType, method) => new {extensionType, method}) + .Where(t => t.method.IsDefined(typeof(ExtensionAttribute), false)) + .Where(t => + t.method.GetParameters()[0].ParameterType == type || + t.method.GetParameters()[0].ParameterType.IsAssignableFrom(type) || + type.GetInterfaces().Contains(t.method.GetParameters()[0].ParameterType)) + .Select(t => t.method); +#pragma warning restore BHI1002 // Do not use anonymous types (classes) +#pragma warning restore MA0029 // Combine LINQ methods + + return query.ToArray(); + } + + /// + /// Extends the System.Type-type to search for a given extended MethodeName. + /// + /// + /// + /// + /// + public static MethodInfo GetExtensionMethod(this Type t, string name, IEnumerable assemblies = null) + { + var mi = t.GetExtensionMethods(name, assemblies).ToArray(); + if (mi.Length == 0) + return null; + return mi[0]; + } + } +} diff --git a/ExternalProjects/NLua/src/GenerateEventAssembly/ClassGenerator.cs b/ExternalProjects/NLua/src/GenerateEventAssembly/ClassGenerator.cs new file mode 100644 index 0000000000..ab3eb1fc63 --- /dev/null +++ b/ExternalProjects/NLua/src/GenerateEventAssembly/ClassGenerator.cs @@ -0,0 +1,21 @@ +using System; + +namespace NLua +{ + internal class ClassGenerator + { + private readonly ObjectTranslator _translator; + private readonly Type _klass; + + public ClassGenerator(ObjectTranslator objTranslator, Type typeClass) + { + _translator = objTranslator; + _klass = typeClass; + } + + public object ExtractGenerated(LuaState luaState, int stackPos) + { + return CodeGeneration.Instance.GetClassInstance(_klass, _translator.GetTable(luaState, stackPos)); + } + } +} diff --git a/ExternalProjects/NLua/src/GenerateEventAssembly/CodeGeneration.cs b/ExternalProjects/NLua/src/GenerateEventAssembly/CodeGeneration.cs new file mode 100644 index 0000000000..2410050f38 --- /dev/null +++ b/ExternalProjects/NLua/src/GenerateEventAssembly/CodeGeneration.cs @@ -0,0 +1,190 @@ +using System; +using System.Threading; +using System.Reflection; + +using System.Reflection.Emit; +using System.Collections.Generic; +using NLua.Method; + +namespace NLua +{ + internal class CodeGeneration + { + private readonly Dictionary _classCollection = new Dictionary(); + private readonly Dictionary _delegateCollection = new Dictionary(); + + static CodeGeneration() + { + } + + private CodeGeneration() + { + } + + /// + /// Singleton instance of the class + /// + public static CodeGeneration Instance { get; } = new CodeGeneration(); + + /// + /// Generates an event handler that calls a Lua function + /// + private Type GenerateEvent(Type eventHandlerType) + { + throw new NotImplementedException("Emit not available on .NET Standard "); + } + + /// + /// Generates a type that can be used for instantiating a delegate + /// of the provided type, given a Lua function. + /// + private Type GenerateDelegate(Type delegateType) + { + throw new NotImplementedException("GenerateDelegate is not available on Windows Store, please register your LuaDelegate type with Lua.RegisterLuaDelegateType( yourDelegate, theLuaDelegateHandler) "); + } + + internal void GetReturnTypesFromClass(Type klass, out Type[][] returnTypes) + { + var classMethods = klass.GetMethods(); + returnTypes = new Type[classMethods.Length][]; + + int i = 0; + + foreach (var method in classMethods) + { + if (klass.IsInterface) + { + GetReturnTypesFromMethod(method, out returnTypes[i]); + i++; + } + else if (!method.IsPrivate && !method.IsFinal && method.IsVirtual) + { + GetReturnTypesFromMethod(method, out returnTypes[i]); + i++; + } + } + } + + /* + * Generates an implementation of klass, if it is an interface, or + * a subclass of klass that delegates its virtual methods to a Lua table. + */ + public void GenerateClass(Type klass, out Type newType, out Type[][] returnTypes) + { + throw new NotImplementedException (" Emit not available on .NET Standard "); + } + + internal void GetReturnTypesFromMethod(MethodInfo method, out Type[] returnTypes) + { + var paramInfo = method.GetParameters(); + var paramTypes = new Type[paramInfo.Length]; + var returnTypesList = new List(); + + // Counts out and ref parameters, for later use, + // and creates the list of return types + int nOutParams = 0; + int nOutAndRefParams = 0; + var returnType = method.ReturnType; + returnTypesList.Add(returnType); + + for (int i = 0; i < paramTypes.Length; i++) + { + paramTypes[i] = paramInfo[i].ParameterType; + + if (!paramInfo[i].IsIn && paramInfo[i].IsOut) + { + nOutParams++; + } + + if (paramTypes[i].IsByRef) + { + returnTypesList.Add(paramTypes[i].GetElementType()); + nOutAndRefParams++; + } + } + + returnTypes = returnTypesList.ToArray(); + } + + /// + /// Gets an event handler for the event type that delegates to the eventHandler Lua function. + /// Caches the generated type. + /// + public LuaEventHandler GetEvent(Type eventHandlerType, LuaFunction eventHandler) + { + throw new NotImplementedException("Emit not available on .NET Standard"); + } + + public void RegisterLuaDelegateType(Type delegateType, Type luaDelegateType) + { + _delegateCollection[delegateType] = luaDelegateType; + } + + public void RegisterLuaClassType(Type klass, Type luaClass) + { + LuaClassType luaClassType = default; + luaClassType.klass = luaClass; + GetReturnTypesFromClass(klass, out luaClassType.returnTypes); + _classCollection[klass] = luaClassType; + } + + /// + /// Gets a delegate with delegateType that calls the luaFunc Lua function + /// Caches the generated type. + /// + public Delegate GetDelegate(Type delegateType, LuaFunction luaFunc) + { + var returnTypes = new List(); + Type luaDelegateType; + + if (_delegateCollection.ContainsKey(delegateType)) + { + luaDelegateType = _delegateCollection[delegateType]; + } + else + { + luaDelegateType = GenerateDelegate(delegateType); + _delegateCollection[delegateType] = luaDelegateType; + } + + var methodInfo = delegateType.GetMethod("Invoke"); + returnTypes.Add(methodInfo.ReturnType); + + foreach (ParameterInfo paramInfo in methodInfo.GetParameters()) + { + if (paramInfo.ParameterType.IsByRef) + returnTypes.Add(paramInfo.ParameterType); + } + + var luaDelegate = (LuaDelegate)Activator.CreateInstance(luaDelegateType); + luaDelegate.Function = luaFunc; + luaDelegate.ReturnTypes = returnTypes.ToArray(); + return Delegate.CreateDelegate(delegateType, luaDelegate, "CallFunction"); + } + + /// + /// Gets an instance of an implementation of the klass interface or + /// subclass of klass that delegates public virtual methods to the + /// luaTable table. + /// Caches the generated type. + /// + public object GetClassInstance(Type klass, LuaTable luaTable) + { + LuaClassType luaClassType; + + if (_classCollection.ContainsKey(klass)) + luaClassType = _classCollection[klass]; + else + { + luaClassType = default; + GenerateClass(klass, out luaClassType.klass, out luaClassType.returnTypes); + _classCollection[klass] = luaClassType; + } + + return Activator.CreateInstance(luaClassType.klass, new object[] { + luaTable, + luaClassType.returnTypes + }); + } + } +} diff --git a/ExternalProjects/NLua/src/GenerateEventAssembly/DelegateGenerator.cs b/ExternalProjects/NLua/src/GenerateEventAssembly/DelegateGenerator.cs new file mode 100644 index 0000000000..e9e23b59b5 --- /dev/null +++ b/ExternalProjects/NLua/src/GenerateEventAssembly/DelegateGenerator.cs @@ -0,0 +1,21 @@ +using System; + +namespace NLua +{ + internal class DelegateGenerator + { + private readonly ObjectTranslator _translator; + private readonly Type _delegateType; + + public DelegateGenerator(ObjectTranslator objectTranslator, Type type) + { + _translator = objectTranslator; + _delegateType = type; + } + + public object ExtractGenerated(LuaState luaState, int stackPos) + { + return CodeGeneration.Instance.GetDelegate(_delegateType, _translator.GetFunction(luaState, stackPos)); + } + } +} diff --git a/ExternalProjects/NLua/src/GenerateEventAssembly/ILuaGeneratedType.cs b/ExternalProjects/NLua/src/GenerateEventAssembly/ILuaGeneratedType.cs new file mode 100644 index 0000000000..8ca2dc4311 --- /dev/null +++ b/ExternalProjects/NLua/src/GenerateEventAssembly/ILuaGeneratedType.cs @@ -0,0 +1,11 @@ +namespace NLua +{ + /// + /// Common interface for types generated from tables. The method + /// returns the table that overrides some or all of the type's methods. + /// + public interface ILuaGeneratedType + { + LuaTable LuaInterfaceGetLuaTable(); + } +} \ No newline at end of file diff --git a/ExternalProjects/NLua/src/GenerateEventAssembly/LuaClassType.cs b/ExternalProjects/NLua/src/GenerateEventAssembly/LuaClassType.cs new file mode 100644 index 0000000000..5f22bc9938 --- /dev/null +++ b/ExternalProjects/NLua/src/GenerateEventAssembly/LuaClassType.cs @@ -0,0 +1,15 @@ +using System; + +namespace NLua +{ + /// + /// Structure to store a type and the return types of + /// its methods (the type of the returned value and out/ref + /// parameters). + /// + internal struct LuaClassType + { + public Type klass; + public Type[][] returnTypes; + } +} \ No newline at end of file diff --git a/ExternalProjects/NLua/src/Lua.cs b/ExternalProjects/NLua/src/Lua.cs new file mode 100644 index 0000000000..69b3c43e7a --- /dev/null +++ b/ExternalProjects/NLua/src/Lua.cs @@ -0,0 +1,1370 @@ +using System; +using System.Linq; +using System.Reflection; +using System.Collections.Generic; +using System.Runtime.InteropServices; + +using NLua.Event; +using NLua.Method; +using NLua.Exceptions; +using NLua.Extensions; + + +namespace NLua +{ + public class Lua : IDisposable + { + /// + /// Event that is raised when an exception occures during a hook call. + /// + public event EventHandler HookException; + + /// + /// Event when lua hook callback is called + /// + /// + /// Is only raised if SetDebugHook is called before. + /// + public event EventHandler DebugHook; + + /// + /// lua hook calback delegate + /// + private LuaHookFunction _hookCallback; + + private readonly LuaGlobals _globals = new LuaGlobals(); + + private LuaState _luaState; + + /// + /// True while a script is being executed + /// + public bool IsExecuting => _executing; + + public LuaState State => _luaState; + + private ObjectTranslator _translator; + + internal ObjectTranslator Translator => _translator; + + private bool _StatePassed; + private bool _executing; + + // The commented code bellow is the initLua, the code assigned here is minified for size/performance reasons. + private const string InitLuanet = @"local a={}local rawget=rawget;local b=luanet.import_type;local c=luanet.load_assembly;luanet.error,luanet.type=error,type;function a:__index(d)local e=rawget(self,'.fqn')e=(e and e..'.'or'')..d;local f=rawget(luanet,d)or b(e)if f==nil then pcall(c,e)f={['.fqn']=e}setmetatable(f,a)end;rawset(self,d,f)return f end;function a:__call(...)error('No such type: '..rawget(self,'.fqn'),2)end;luanet['.fqn']=false;setmetatable(luanet,a)luanet.load_assembly('mscorlib')"; + //@"local metatable = {} + // local rawget = rawget + // local import_type = luanet.import_type + // local load_assembly = luanet.load_assembly + // luanet.error, luanet.type = error, type + // -- Lookup a .NET identifier component. + // function metatable:__index(key) -- key is e.g. 'Form' + // -- Get the fully-qualified name, e.g. 'System.Windows.Forms.Form' + // local fqn = rawget(self,'.fqn') + // fqn = ((fqn and fqn .. '.') or '') .. key + + // -- Try to find either a luanet function or a CLR type + // local obj = rawget(luanet,key) or import_type(fqn) + + // -- If key is neither a luanet function or a CLR type, then it is simply + // -- an identifier component. + // if obj == nil then + // -- It might be an assembly, so we load it too. + // pcall(load_assembly,fqn) + // obj = { ['.fqn'] = fqn } + // setmetatable(obj, metatable) + // end + + // -- Cache this lookup + // rawset(self, key, obj) + // return obj + // end + + // -- A non-type has been called; e.g. foo = System.Foo() + // function metatable:__call(...) + // error('No such type: ' .. rawget(self,'.fqn'), 2) + // end + + // -- This is the root of the .NET namespace + // luanet['.fqn'] = false + // setmetatable(luanet, metatable) + + // -- Preload the mscorlib assembly + // luanet.load_assembly('mscorlib')"; + + private const string ClrPackage = @"if not luanet then require'luanet'end;local a,b=luanet.import_type,luanet.load_assembly;local c={__index=function(d,e)local f=rawget(d,e)if f==nil then f=a(d.packageName.."".""..e)if f==nil then f=a(e)end;d[e]=f end;return f end}function luanet.namespace(g)if type(g)=='table'then local h={}for i=1,#g do h[i]=luanet.namespace(g[i])end;return unpack(h)end;local j={packageName=g}setmetatable(j,c)return j end;local k,l;local function m()l={}k={__index=function(n,e)for i,d in ipairs(l)do local f=d[e]if f then _G[e]=f;return f end end end}setmetatable(_G,k)end;function CLRPackage(o,p)p=p or o;local q=pcall(b,o)return luanet.namespace(p)end;function import(o,p)if not k then m()end;if not p then local i=o:find('%.dll$')if i then p=o:sub(1,i-1)else p=o end end;local j=CLRPackage(o,p)table.insert(l,j)return j end;function luanet.make_array(r,s)local t=r[#s]for i,u in ipairs(s)do t:SetValue(u,i-1)end;return t end;function luanet.each(v)local w=v:GetEnumerator()return function()if w:MoveNext()then return w.Current end end end"; + //@"--- + //--- This lua module provides auto importing of .net classes into a named package. + //--- Makes for super easy use of LuaInterface glue + //--- + //--- example: + //--- Threading = CLRPackage(""System"", ""System.Threading"") + //--- Threading.Thread.Sleep(100) + //--- + //--- Extensions: + //--- import() is a version of CLRPackage() which puts the package into a list which is used by a global __index lookup, + //--- and thus works rather like C#'s using statement. It also recognizes the case where one is importing a local + //--- assembly, which must end with an explicit .dll extension. + + //--- Alternatively, luanet.namespace can be used for convenience without polluting the global namespace: + //--- local sys,sysi = luanet.namespace {'System','System.IO'} + //-- sys.Console.WriteLine(""we are at {0}"",sysi.Directory.GetCurrentDirectory()) + + + //-- LuaInterface hosted with stock Lua interpreter will need to explicitly require this... + //if not luanet then require 'luanet' end + + //local import_type, load_assembly = luanet.import_type, luanet.load_assembly + + //local mt = { + // --- Lookup a previously unfound class and add it to our table + // __index = function(package, classname) + // local class = rawget(package, classname) + // if class == nil then + // class = import_type(package.packageName .. ""."" .. classname) + // if class == nil then class = import_type(classname) end + // package[classname] = class -- keep what we found around, so it will be shared + // end + // return class + // end + //} + + //function luanet.namespace(ns) + // if type(ns) == 'table' then + // local res = {} + // for i = 1,#ns do + // res[i] = luanet.namespace(ns[i]) + // end + // return unpack(res) + // end + // -- FIXME - table.packageName could instead be a private index (see Lua 13.4.4) + // local t = { packageName = ns } + // setmetatable(t,mt) + // return t + //end + + //local globalMT, packages + + //local function set_global_mt() + // packages = {} + // globalMT = { + // __index = function(T,classname) + // for i,package in ipairs(packages) do + // local class = package[classname] + // if class then + // _G[classname] = class + // return class + // end + // end + // end + // } + // setmetatable(_G, globalMT) + //end + + //--- Create a new Package class + //function CLRPackage(assemblyName, packageName) + // -- a sensible default... + // packageName = packageName or assemblyName + // local ok = pcall(load_assembly,assemblyName) -- Make sure our assembly is loaded + // return luanet.namespace(packageName) + //end + + //function import (assemblyName, packageName) + // if not globalMT then + // set_global_mt() + // end + // if not packageName then + // local i = assemblyName:find('%.dll$') + // if i then packageName = assemblyName:sub(1,i-1) + // else packageName = assemblyName end + // end + // local t = CLRPackage(assemblyName,packageName) + // table.insert(packages,t) + // return t + //end + + + //function luanet.make_array (tp,tbl) + // local arr = tp[#tbl] + // for i,v in ipairs(tbl) do + // arr:SetValue(v,i-1) + // end + // return arr + //end + + //function luanet.each(o) + // local e = o:GetEnumerator() + // return function() + // if e:MoveNext() then + // return e.Current + // end + // end + //end + //"; + + public bool UseTraceback { get; set; } = false; + + /// + /// The maximum number of recursive steps to take when adding global reference variables. Defaults to 2. + /// + public int MaximumRecursion + { + get + { + return _globals.MaximumRecursion; + } + set + { + _globals.MaximumRecursion = value; + } + } + + /// + /// 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 + { + return _globals.Globals; + } + } + + /// + /// Get the thread object of this state. + /// + public LuaThread Thread + { + get + { + int oldTop = _luaState.GetTop(); + _luaState.PushThread(); + object returnValue = _translator.GetObject(_luaState, -1); + + _luaState.SetTop(oldTop); + return (LuaThread)returnValue; + } + } + + /// + /// Get the main thread object + /// + public LuaThread MainThread + { + get + { + LuaState mainThread = _luaState.MainThread; + int oldTop = mainThread.GetTop(); + mainThread.PushThread(); + object returnValue = _translator.GetObject(mainThread, -1); + + mainThread.SetTop(oldTop); + return (LuaThread)returnValue; + } + } + + public Lua(bool openLibs = true) + { + _luaState = new LuaState(openLibs); + Init(); + // We need to keep this in a managed reference so the delegate doesn't get garbage collected + _luaState.AtPanic(PanicCallback); + } + + // CAUTION: NLua.Lua instances can't share the same lua state! + public Lua(LuaState luaState) + { + luaState.PushString("NLua_Loaded"); + luaState.GetTable((int)LuaRegistry.Index); + + if (luaState.ToBoolean(-1)) + { + luaState.SetTop(-2); + throw new LuaException("There is already a NLua.Lua instance associated with this Lua state"); + } + + _luaState = luaState; + _StatePassed = true; + luaState.SetTop(-2); + Init(); + } + + internal void Init() + { + _luaState.PushString("NLua_Loaded"); + _luaState.PushBoolean(true); + _luaState.SetTable((int)LuaRegistry.Index); + if (_StatePassed == false) + { + _luaState.NewTable(); + _luaState.SetGlobal("luanet"); + } + _luaState.PushGlobalTable(); + _luaState.GetGlobal("luanet"); + _luaState.PushString("getmetatable"); + _luaState.GetGlobal("getmetatable"); + _luaState.SetTable(-3); + _luaState.PopGlobalTable(); + _translator = new ObjectTranslator(this, _luaState); + + ObjectTranslatorPool.Instance.Add(_luaState, _translator); + + _luaState.PopGlobalTable(); + _luaState.DoString(InitLuanet); + } + + public void Close() + { + if (_StatePassed || _luaState == null) + return; + + _luaState.Close(); + ObjectTranslatorPool.Instance.Remove(_luaState); + _luaState = null; + } + + internal static int PanicCallback(IntPtr state) + { + var luaState = LuaState.FromIntPtr(state); + string reason = string.Format("Unprotected error in call to Lua API ({0})", luaState.ToString(-1, false)); + 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 + private void ThrowExceptionFromError(int oldTop) + { + object err = _translator.GetObject(_luaState, -1); + _luaState.SetTop(oldTop); + + // A pre-wrapped exception - just rethrow it (stack trace of InnerException will be preserved) + var 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(), string.Empty); + } + + /// + /// Push a debug.traceback reference onto the stack, for a pcall function to use as error handler. (Remember to increment any top-of-stack markers!) + /// + private static int PushDebugTraceback(LuaState luaState, int argCount) + { + luaState.GetGlobal("debug"); + luaState.GetField(-1, "traceback"); + luaState.Remove(-2); + int errIndex = -argCount - 2; + luaState.Insert(errIndex); + return errIndex; + } + + /// + /// Return a debug.traceback() call result (a multi-line string, containing a full stack trace, including C calls. + /// Note: it won't return anything unless the interpreter is in the middle of execution - that is, it only makes sense to call it from a method called from Lua, or during a coroutine yield. + /// + public string GetDebugTraceback() + { + int oldTop = _luaState.GetTop(); + _luaState.GetGlobal("debug"); // stack: debug + _luaState.GetField(-1, "traceback"); // stack: debug,traceback + _luaState.Remove(-2); // stack: traceback + _luaState.PCall(0, -1, 0); + return _translator.PopValues(_luaState, oldTop)[0] as string; + } + + /// + /// Convert C# exceptions into Lua errors + /// + /// num of things on stack + /// null for no pending exception + internal int SetPendingException(Exception e) + { + var caughtExcept = e; + + if (caughtExcept == null) + return 0; + + _translator.ThrowError(_luaState, caughtExcept); + return 1; + } + + /// + /// + /// + /// + /// + /// + public LuaFunction LoadString(string chunk, string name) + { + int oldTop = _luaState.GetTop(); + _executing = true; + + try + { + if (_luaState.LoadString(chunk, name) != LuaStatus.OK) + ThrowExceptionFromError(oldTop); + } + finally + { + _executing = false; + } + + var result = _translator.GetFunction(_luaState, -1); + _translator.PopValues(_luaState, oldTop); + return result; + } + + /// + /// + /// + /// + /// + /// + public LuaFunction LoadString(byte[] chunk, string name) + { + int oldTop = _luaState.GetTop(); + _executing = true; + + try + { + if (_luaState.LoadBuffer(chunk, name) != LuaStatus.OK) + ThrowExceptionFromError(oldTop); + } + finally + { + _executing = false; + } + + var result = _translator.GetFunction(_luaState, -1); + _translator.PopValues(_luaState, oldTop); + return result; + } + + /// + /// Load a File on, and return a LuaFunction to execute the file loaded (useful to see if the syntax of a file is ok) + /// + /// + /// + public LuaFunction LoadFile(string fileName) + { + int oldTop = _luaState.GetTop(); + + if (_luaState.LoadFile(fileName) != LuaStatus.OK) + ThrowExceptionFromError(oldTop); + + var result = _translator.GetFunction(_luaState, -1); + _translator.PopValues(_luaState, oldTop); + return result; + } + + /// + /// Executes a Lua chunk and returns all the chunk's return values in an array. + /// + /// Chunk to execute + /// Name to associate with the chunk. Defaults to "chunk". + /// + public object[] DoString(byte[] chunk, string chunkName = "chunk") + { + int oldTop = _luaState.GetTop(); + _executing = true; + + if (_luaState.LoadBuffer(chunk, chunkName) != LuaStatus.OK) + ThrowExceptionFromError(oldTop); + + int errorFunctionIndex = 0; + + if (UseTraceback) + { + errorFunctionIndex = PushDebugTraceback(_luaState, 0); + oldTop++; + } + + try + { + if (_luaState.PCall(0, -1, errorFunctionIndex) != LuaStatus.OK) + ThrowExceptionFromError(oldTop); + + return _translator.PopValues(_luaState, oldTop); + } + finally + { + _executing = false; + } + } + + /// + /// Executes a Lua chunk and returns all the chunk's return values in an array. + /// + /// Chunk to execute + /// Name to associate with the chunk. Defaults to "chunk". + /// + public object[] DoString(string chunk, string chunkName = "chunk") + { + int oldTop = _luaState.GetTop(); + _executing = true; + + if (_luaState.LoadString(chunk, chunkName) != LuaStatus.OK) + ThrowExceptionFromError(oldTop); + + int errorFunctionIndex = 0; + + if (UseTraceback) + { + errorFunctionIndex = PushDebugTraceback(_luaState, 0); + oldTop++; + } + + try + { + if (_luaState.PCall(0, -1, errorFunctionIndex) != LuaStatus.OK) + ThrowExceptionFromError(oldTop); + + return _translator.PopValues(_luaState, oldTop); + } + finally + { + _executing = false; + } + } + + /// + /// Executes a Lua file and returns all the chunk's return + /// values in an array + /// + public object[] DoFile(string fileName) + { + int oldTop = _luaState.GetTop(); + + if (_luaState.LoadFile(fileName) != LuaStatus.OK) + ThrowExceptionFromError(oldTop); + + _executing = true; + + int errorFunctionIndex = 0; + if (UseTraceback) + { + errorFunctionIndex = PushDebugTraceback(_luaState, 0); + oldTop++; + } + + try + { + if (_luaState.PCall(0, -1, errorFunctionIndex) != LuaStatus.OK) + ThrowExceptionFromError(oldTop); + + return _translator.PopValues(_luaState, oldTop); + } + finally + { + _executing = false; + } + } + + public object GetObjectFromPath(string fullPath) + { + int oldTop = _luaState.GetTop(); + string[] path = FullPathToArray(fullPath); + _luaState.GetGlobal(path[0]); + object returnValue = _translator.GetObject(_luaState, -1); + + if (path.Length > 1) + { + var dispose = returnValue as LuaBase; + string[] remainingPath = new string[path.Length - 1]; + Array.Copy(path, 1, remainingPath, 0, path.Length - 1); + returnValue = GetObject(remainingPath); + dispose?.Dispose(); + } + + _luaState.SetTop(oldTop); + return returnValue; + } + + public void SetObjectToPath(string fullPath, object value) + { + int oldTop = _luaState.GetTop(); + string[] path = FullPathToArray(fullPath); + + if (path.Length == 1) + { + _translator.Push(_luaState, value); + _luaState.SetGlobal(fullPath); + } + else + { + _luaState.GetGlobal(path[0]); + string[] remainingPath = new string[path.Length - 1]; + Array.Copy(path, 1, remainingPath, 0, path.Length - 1); + SetObject(remainingPath, value); + } + + _luaState.SetTop(oldTop); + + // Globals auto-complete + if (value == null) + { + // Remove now obsolete entries + _globals.RemoveGlobal(fullPath); + } + else + { + // Add new entries + if (!_globals.Contains(fullPath)) + _globals.RegisterGlobal(fullPath, value.GetType(), 0); + } + } + + /// + /// Indexer for global variables from the LuaInterpreter + /// Supports navigation of tables by using . operator + /// + /// + /// + public object this[string fullPath] { + get + { + // Silently convert Lua integer to double for backward compatibility with index[] operator + object obj = GetObjectFromPath(fullPath); + if (obj is long l) + return (double)l; + return obj; + } + set + { + SetObjectToPath(fullPath, value); + } + } + + /// + /// 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 < remainingPath.Length; i++) + { + _luaState.PushString(remainingPath[i]); + _luaState.GetTable(-2); + returnValue = _translator.GetObject(_luaState, -1); + + if (returnValue == null) + break; + } + + return returnValue; + } + + /// + /// Gets a numeric global variable + /// + public double GetNumber(string fullPath) + { + // Silently convert Lua integer to double for backward compatibility with GetNumber method + object obj = GetObjectFromPath(fullPath); + if (obj is long l) + return l; + return (double)obj; + } + + public int GetInteger(string fullPath) + { + object result = GetObjectFromPath(fullPath); + if (result == null) + return 0; + + return (int)(long)result; + } + + public long GetLong(string fullPath) + { + object result = GetObjectFromPath(fullPath); + if (result == null) + return 0L; + + return (long)result; + } + + /// + /// Gets a string global variable + /// + public string GetString(string fullPath) + { + object obj = GetObjectFromPath(fullPath); + if (obj == null) + return null; + + return obj.ToString(); + } + + /// + /// Gets a table global variable + /// + public LuaTable GetTable(string fullPath) + { + return (LuaTable)GetObjectFromPath(fullPath); + } + + /// + /// Gets a table global variable as an object implementing + /// the interfaceType interface + /// + public object GetTable(Type interfaceType, string fullPath) + { + return CodeGeneration.Instance.GetClassInstance(interfaceType, GetTable(fullPath)); + } + + /// + /// Gets a thread global variable + /// + public LuaThread GetThread(string fullPath) + { + return (LuaThread)GetObjectFromPath(fullPath); + } + + /// + /// Gets a function global variable + /// + public LuaFunction GetFunction(string fullPath) + { + object obj = GetObjectFromPath(fullPath); + var luaFunction = obj as LuaFunction; + if (luaFunction != null) + return luaFunction; + + luaFunction = new LuaFunction((LuaNativeFunction) obj, this); + return luaFunction; + } + + /// + /// Register a delegate type to be used to convert Lua functions to C# delegates (useful for iOS where there is no dynamic code generation) + /// type delegateType + /// + public void RegisterLuaDelegateType(Type delegateType, Type luaDelegateType) + { + CodeGeneration.Instance.RegisterLuaDelegateType(delegateType, luaDelegateType); + } + + public void RegisterLuaClassType(Type klass, Type luaClass) + { + CodeGeneration.Instance.RegisterLuaClassType(klass, luaClass); + } + + // ReSharper disable once InconsistentNaming + public void LoadCLRPackage() + { + _luaState.DoString(ClrPackage); + } + + /// + /// Gets a function global variable as a delegate of + /// type delegateType + /// + public Delegate GetFunction(Type delegateType, string fullPath) + { + return CodeGeneration.Instance.GetDelegate(delegateType, GetFunction(fullPath)); + } + + /// + /// Calls the object as a function with the provided arguments, + /// returning the function's returned values inside an array + /// + internal object[] CallFunction(object function, object[] args) + { + return CallFunction(function, args, null); + } + + /// + /// Calls the object as a function with the provided arguments and + /// casting returned values to the types in returnTypes before returning + /// them in an array + /// + internal object[] CallFunction(object function, object[] args, Type[] returnTypes) + { + int nArgs = 0; + int oldTop = _luaState.GetTop(); + + if (!_luaState.CheckStack(args.Length + 6)) + throw new LuaException("Lua stack overflow"); + + _translator.Push(_luaState, function); + + if (args.Length > 0) + { + nArgs = args.Length; + + for (int i = 0; i < args.Length; i++) + _translator.Push(_luaState, args[i]); + } + + _executing = true; + + try + { + int errfunction = 0; + if (UseTraceback) + { + errfunction = PushDebugTraceback(_luaState, nArgs); + oldTop++; + } + + LuaStatus error = _luaState.PCall(nArgs, -1, errfunction); + if (error != LuaStatus.OK) + ThrowExceptionFromError(oldTop); + } + finally + { + _executing = false; + } + + if (returnTypes != null) + return _translator.PopValues(_luaState, oldTop, returnTypes); + + return _translator.PopValues(_luaState, oldTop); + } + + /// + /// Navigates a table to set the value of one of its fields + /// + internal void SetObject(string[] remainingPath, object val) + { + for (int i = 0; i < remainingPath.Length - 1; i++) + { + _luaState.PushString(remainingPath[i]); + _luaState.GetTable(-2); + } + + _luaState.PushString(remainingPath[remainingPath.Length - 1]); + _translator.Push(_luaState, val); + _luaState.SetTable(-3); + } + + /// + /// Creates a new empty table + /// + public LuaTable NewTable() + { + int oldTop = _luaState.GetTop(); + _luaState.NewTable(); + var ret = _translator.GetTable(_luaState, -1); + _luaState.SetTop(oldTop); + return ret; + } + + internal string[] FullPathToArray(string fullPath) + { + return fullPath.SplitWithEscape('.', '\\').ToArray(); + } + + /// + /// Creates a new table as a global variable or as a field + /// inside an existing table + /// + /// + public void NewTable(string fullPath) + { + string[] path = FullPathToArray(fullPath); + int oldTop = _luaState.GetTop(); + + if (path.Length == 1) + { + _luaState.NewTable(); + _luaState.SetGlobal(fullPath); + } + else + { + _luaState.GetGlobal(path[0]); + + for (int i = 1; i < path.Length - 1; i++) + { + _luaState.PushString(path[i]); + _luaState.GetTable(-2); + } + + _luaState.PushString(path[path.Length - 1]); + _luaState.NewTable(); + _luaState.SetTable(-3); + } + + _luaState.SetTop( oldTop); + } + + public Dictionary GetTableDict(LuaTable table) + { + if (table == null) + throw new ArgumentNullException(nameof(table)); + + var dict = new Dictionary(); + int oldTop = _luaState.GetTop(); + _translator.Push(_luaState, table); + _luaState.PushNil(); + + while (_luaState.Next(-2)) + { + dict[_translator.GetObject(_luaState, -2)] = _translator.GetObject(_luaState, -1); + _luaState.SetTop(-2); + } + + _luaState.SetTop(oldTop); + return dict; + } + + /// + /// Activates the debug hook + /// + /// Mask + /// Count + /// see lua docs. -1 if hook is already set + public int SetDebugHook(LuaHookMask mask, int count) + { + if (_hookCallback == null) + { + _hookCallback = DebugHookCallback; + _luaState.SetHook(_hookCallback, mask, count); + } + + return -1; + } + + /// + /// Removes the debug hook + /// + public void RemoveDebugHook() + { + _hookCallback = null; + _luaState.SetHook(null, LuaHookMask.Disabled, 0); + } + + /// + /// Gets the hook mask. + /// + /// hook mask + public LuaHookMask GetHookMask() + { + return _luaState.HookMask; + } + + /// + /// Gets the hook count + /// + /// see lua docs + public int GetHookCount() + { + return _luaState.HookCount; + } + + + /// + /// Gets local (see lua docs) + /// + /// lua debug structure + /// see lua docs + /// see lua docs + public string GetLocal(LuaDebug luaDebug, int n) + { + return _luaState.GetLocal(luaDebug, n); + } + + /// + /// Sets local (see lua docs) + /// + /// lua debug structure + /// see lua docs + /// see lua docs + public string SetLocal(LuaDebug luaDebug, int n) + { + return _luaState.SetLocal(luaDebug, n); + } + + public int GetStack(int level, ref LuaDebug ar) + { + return _luaState.GetStack(level, ref ar); + } + + public bool GetInfo(string what, ref LuaDebug ar) + { + return _luaState.GetInfo(what, ref ar); + } + + /// + /// Gets up value (see lua docs) + /// + /// see lua docs + /// see lua docs + /// see lua docs + public string GetUpValue(int funcindex, int n) + { + return _luaState.GetUpValue(funcindex, n); + } + + /// + /// Sets up value (see lua docs) + /// + /// see lua docs + /// see lua docs + /// see lua docs + public string SetUpValue(int funcindex, int n) + { + return _luaState.SetUpValue(funcindex, n); + } + + /// + /// Delegate that is called on lua hook callback + /// + /// lua state + /// Pointer to LuaDebug (lua_debug) structure + internal static void DebugHookCallback(IntPtr luaState, IntPtr luaDebug) + { + var state = LuaState.FromIntPtr(luaState); + + state.GetStack(0, luaDebug); + + if (!state.GetInfo("Snlu", luaDebug)) + return; + + var debug = LuaDebug.FromIntPtr(luaDebug); + + ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(state); + Lua lua = translator.Interpreter; + lua.DebugHookCallbackInternal(debug); + } + + private void DebugHookCallbackInternal(LuaDebug luaDebug) + { + try + { + var temp = DebugHook; + + if (temp != null) + temp(this, new DebugHookEventArgs(luaDebug)); + } + catch (Exception ex) + { + OnHookException(new HookExceptionEventArgs(ex)); + } + } + + private void OnHookException(HookExceptionEventArgs e) + { + var temp = HookException; + if (temp != null) + temp(this, e); + } + + /// + /// Pops a value from the lua stack. + /// + /// Returns the top value from the lua stack. + public object Pop() + { + int top = _luaState.GetTop(); + return _translator.PopValues(_luaState, top - 1)[0]; + } + + /// + /// Pushes a value onto the lua stack. + /// + /// Value to push. + public void Push(object value) + { + _translator.Push(_luaState, value); + } + + internal void DisposeInternal(int reference, bool finalized) + { + if (finalized && _translator != null) + { + _translator.AddFinalizedReference(reference); + return; + } + + if (_luaState != null && !finalized) + _luaState.Unref(reference); + } + + /// + /// Gets a field of the table corresponding to the provided reference + /// using rawget (do not use metatables) + /// + internal object RawGetObject(int reference, string field) + { + int oldTop = _luaState.GetTop(); + _luaState.GetRef(reference); + _luaState.PushString(field); + _luaState.RawGet(-2); + object obj = _translator.GetObject(_luaState, -1); + _luaState.SetTop(oldTop); + return obj; + } + + /// + /// Gets a field of the table or userdata corresponding to the provided reference + /// + internal object GetObject(int reference, string field) + { + int oldTop = _luaState.GetTop(); + _luaState.GetRef(reference); + object returnValue = GetObject(FullPathToArray(field)); + _luaState.SetTop(oldTop); + return returnValue; + } + + /// + /// Gets a numeric field of the table or userdata corresponding the the provided reference + /// + internal object GetObject(int reference, object field) + { + int oldTop = _luaState.GetTop(); + _luaState.GetRef(reference); + _translator.Push(_luaState, field); + _luaState.GetTable(-2); + object returnValue = _translator.GetObject(_luaState, -1); + _luaState.SetTop(oldTop); + return returnValue; + } + + /// + /// Sets a field of the table or userdata corresponding the the provided reference + /// to the provided value + /// + internal void SetObject(int reference, string field, object val) + { + int oldTop = _luaState.GetTop(); + _luaState.GetRef(reference); + SetObject(FullPathToArray(field), val); + _luaState.SetTop(oldTop); + } + + /// + /// Sets a numeric field of the table or userdata corresponding the the provided reference + /// to the provided value + /// + internal void SetObject(int reference, object field, object val) + { + int oldTop = _luaState.GetTop(); + _luaState.GetRef(reference); + _translator.Push(_luaState, field); + _translator.Push(_luaState, val); + _luaState.SetTable(-3); + _luaState.SetTop(oldTop); + } + + /// + /// Gets the luaState from the thread + /// + internal LuaState GetThreadState(int reference) + { + int oldTop = _luaState.GetTop(); + _luaState.GetRef(reference); + LuaState state = _luaState.ToThread(-1); + _luaState.SetTop(oldTop); + return state; + } + + public void XMove(LuaState to, object val, int index = 1) + { + int oldTop = _luaState.GetTop(); + + _translator.Push(_luaState, val); + _luaState.XMove(to, index); + + _luaState.SetTop(oldTop); + } + + public void XMove(Lua to, object val, int index = 1) + { + int oldTop = _luaState.GetTop(); + + _translator.Push(_luaState, val); + _luaState.XMove(to._luaState, index); + + _luaState.SetTop(oldTop); + } + + public void XMove(LuaThread thread, object val, int index = 1) + { + int oldTop = _luaState.GetTop(); + + _translator.Push(_luaState, val); + _luaState.XMove(thread.State, index); + + _luaState.SetTop(oldTop); + } + + /// + /// Creates a new empty thread + /// + public LuaState NewThread(out LuaThread thread) + { + int oldTop = _luaState.GetTop(); + + LuaState state = _luaState.NewThread(); + thread = (LuaThread)_translator.GetObject(_luaState, -1); + + _luaState.SetTop(oldTop); + return state; + } + + /// + /// Creates a new empty thread as a global variable or as a field + /// inside an existing table + /// + public LuaState NewThread(string fullPath) + { + string[] path = FullPathToArray(fullPath); + int oldTop = _luaState.GetTop(); + + LuaState state; + + if (path.Length == 1) + { + state = _luaState.NewThread(); + _luaState.SetGlobal(fullPath); + } + else + { + _luaState.GetGlobal(path[0]); + + for (int i = 1; i < path.Length - 1; i++) + { + _luaState.PushString(path[i]); + _luaState.GetTable(-2); + } + + _luaState.PushString(path[path.Length - 1]); + state = _luaState.NewThread(); + _luaState.SetTable(-3); + } + + _luaState.SetTop(oldTop); + return state; + } + + /// + /// Creates a new coroutine thread + /// + public LuaState NewThread(LuaFunction function, out LuaThread thread) + { + int oldTop = _luaState.GetTop(); + + LuaState state = _luaState.NewThread(); + thread = (LuaThread)_translator.GetObject(_luaState, -1); + + _translator.Push(_luaState, function); + _luaState.XMove(state, 1); + + _luaState.SetTop(oldTop); + return state; + } + + /// + /// Creates a new coroutine thread as a global variable or as a field + /// inside an existing table + /// + public void NewThread(string fullPath, LuaFunction function) + { + string[] path = FullPathToArray(fullPath); + int oldTop = _luaState.GetTop(); + + LuaState state; + + if (path.Length == 1) + { + state = _luaState.NewThread(); + _luaState.SetGlobal(fullPath); + } + else + { + _luaState.GetGlobal(path[0]); + + for (int i = 1; i < path.Length - 1; i++) + { + _luaState.PushString(path[i]); + _luaState.GetTable(-2); + } + + _luaState.PushString(path[path.Length - 1]); + state = _luaState.NewThread(); + _luaState.SetTable(-3); + } + + _translator.Push(_luaState, function); + _luaState.XMove(state, 1); + + _luaState.SetTop(oldTop); + } + + public LuaFunction RegisterFunction(string path, MethodBase function) + { + return RegisterFunction(path, null, function); + } + + /// + /// Registers an object's method as a Lua function (global or table field) + /// The method may have any signature + /// + public LuaFunction RegisterFunction(string path, object target, MethodBase function) + { + // We leave nothing on the stack when we are done + int oldTop = _luaState.GetTop(); + var wrapper = new LuaMethodWrapper(_translator, target, new ProxyType(function.DeclaringType), function); + + _translator.Push(_luaState, new LuaNativeFunction(wrapper.InvokeFunction)); + + object value = _translator.GetObject(_luaState, -1); + SetObjectToPath(path, value); + + LuaFunction f = GetFunction(path); + _luaState.SetTop(oldTop); + return f; + } + + /// + /// Compares the two values referenced by ref1 and ref2 for equality + /// + internal bool CompareRef(int ref1, int ref2) + { + int top = _luaState.GetTop(); + _luaState.GetRef(ref1); + _luaState.GetRef(ref2); + bool equal = _luaState.AreEqual(-1, -2); + _luaState.SetTop(top); + return equal; + } + + // ReSharper disable once InconsistentNaming + internal void PushCSFunction(LuaNativeFunction function) + { + _translator.PushFunction(_luaState, function); + } + + ~Lua() + { + Dispose(); + } + + public virtual void Dispose() + { + if (_translator != null) + { + _translator.PendingEvents.Dispose(); + if (_translator.Tag != IntPtr.Zero) + Marshal.FreeHGlobal(_translator.Tag); + _translator = null; + } + + Close(); + GC.SuppressFinalize(this); + } + } +} diff --git a/ExternalProjects/NLua/src/LuaBase.cs b/ExternalProjects/NLua/src/LuaBase.cs new file mode 100644 index 0000000000..27d35c87e3 --- /dev/null +++ b/ExternalProjects/NLua/src/LuaBase.cs @@ -0,0 +1,88 @@ +using System; + +namespace NLua +{ + /// + /// Base class to provide consistent disposal flow across lua objects. Uses code provided by Yves Duhoux and suggestions by Hans Schmeidenbacher and Qingrui Li + /// + public abstract class LuaBase : IDisposable + { + private bool _disposed; + protected readonly int _Reference; + internal Lua _lua; + + protected bool TryGet(out Lua lua) + { + if (_lua.State == null) + { + lua = null; + return false; + } + + lua = _lua; + return true; + } + + protected LuaBase(int reference, Lua lua) + { + _lua = lua; + _Reference = reference; + } + + ~LuaBase() + { + Dispose(false); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + internal void DisposeLuaReference(bool finalized) + { + if (_lua == null) + return; + Lua lua; + if (!TryGet(out lua)) + return; + + lua.DisposeInternal(_Reference, finalized); + } + + public virtual void Dispose(bool disposeManagedResources) + { + if (_disposed) + return; + + bool finalized = !disposeManagedResources; + + if (_Reference != 0) + { + DisposeLuaReference(finalized); + } + + _lua = null; + _disposed = true; + } + + public override bool Equals(object o) + { + var reference = o as LuaBase; + if (reference == null) + return false; + + Lua lua; + if (!TryGet(out lua)) + return false; + + return lua.CompareRef(reference._Reference, _Reference); + } + + public override int GetHashCode() + { + return _Reference; + } + } +} \ No newline at end of file diff --git a/ExternalProjects/NLua/src/LuaFunction.cs b/ExternalProjects/NLua/src/LuaFunction.cs new file mode 100644 index 0000000000..7e8b25e3fb --- /dev/null +++ b/ExternalProjects/NLua/src/LuaFunction.cs @@ -0,0 +1,87 @@ +using System; + +namespace NLua +{ + public class LuaFunction : LuaBase + { + internal readonly LuaNativeFunction function; + + public LuaFunction(int reference, Lua interpreter):base(reference, interpreter) + { + function = null; + } + + public LuaFunction(LuaNativeFunction nativeFunction, Lua interpreter):base (0, interpreter) + { + function = nativeFunction; + } + + /// + /// Calls the function casting return values to the types + /// in returnTypes + /// + internal object[] Call(object[] args, Type[] returnTypes) + { + Lua lua; + if (!TryGet(out lua)) + return null; + + return lua.CallFunction(this, args, returnTypes); + } + + /// + /// Calls the function and returns its return values inside + /// an array + /// + public object[] Call(params object[] args) + { + Lua lua; + if (!TryGet(out lua)) + return null; + + return lua.CallFunction(this, args); + } + + /* + * Pushes the function into the Lua stack + */ + internal void Push(LuaState luaState) + { + Lua lua; + if (!TryGet(out lua)) + return; + + if (_Reference != 0) + luaState.RawGetInteger(LuaRegistry.Index, _Reference); + else + lua.PushCSFunction(function); + } + + public override string ToString() + { + return "function"; + } + + public override bool Equals(object o) + { + var l = o as LuaFunction; + + if (l == null) + return false; + + Lua lua; + if (!TryGet(out lua)) + return false; + + if (_Reference != 0 && l._Reference != 0) + return lua.CompareRef(l._Reference, _Reference); + + return function == l.function; + } + + public override int GetHashCode() + { + return _Reference != 0 ? _Reference : function.GetHashCode(); + } + } +} diff --git a/ExternalProjects/NLua/src/LuaGlobalAttribute.cs b/ExternalProjects/NLua/src/LuaGlobalAttribute.cs new file mode 100644 index 0000000000..03d16db93e --- /dev/null +++ b/ExternalProjects/NLua/src/LuaGlobalAttribute.cs @@ -0,0 +1,23 @@ +using System; + +namespace NLua +{ + /// + /// Marks a method for global usage in Lua scripts + /// + /// + /// + [AttributeUsage(AttributeTargets.Method)] + public sealed class LuaGlobalAttribute : Attribute + { + /// + /// An alternative name to use for calling the function in Lua - leave empty for CLR name + /// + public string Name { get; set; } + + /// + /// A description of the function + /// + public string Description { get; set; } + } +} \ No newline at end of file diff --git a/ExternalProjects/NLua/src/LuaGlobals.cs b/ExternalProjects/NLua/src/LuaGlobals.cs new file mode 100644 index 0000000000..e45366d6fc --- /dev/null +++ b/ExternalProjects/NLua/src/LuaGlobals.cs @@ -0,0 +1,201 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace NLua +{ + public class LuaGlobalEntry + { + /// + /// Type at time of registration. + /// + public Type Type { get; private set; } + + public string Path { get; private set; } + + /// + /// List of global properties 'owned' by this entry. + /// If this entry is removed all these globals should be removed as well. + /// + public List linkedGlobals = new List(); + + public LuaGlobalEntry(Type type, string path) + { + Type = type; + Path = path; + } + } + + public class LuaGlobals + { + private List _globals = new List(); + private List _knownTypes = new List(); + + public bool _globalsSorted = false; + + public int MaximumRecursion { get; set; } = 2; + + public IEnumerable Globals + { + get + { + // Only sort list when necessary + if (!_globalsSorted) + { + _globals.Sort(); + _globalsSorted = true; + } + + return _globals; + } + } + + public bool Contains(string fullPath) + { + return _globals.Contains(fullPath); + } + + public void RemoveGlobal(string path) + { + var knownType = GetKnownType(path); + if (knownType != null) + { + // We need to clean up the globals + foreach (var dependent in knownType.linkedGlobals) + { + _globals.Remove(dependent); + } + + _knownTypes.Remove(knownType); + } + } + + private LuaGlobalEntry GetKnownType(string path) + { + return _knownTypes.Find(x => x.Path.Equals(path)); + } + + public void RegisterGlobal(string path, Type type, int recursionCounter) + { + var knownType = GetKnownType(path); + if (knownType != null) + { + if (type.Equals(knownType.Type)) + { + // Object is set to same value so no need to update + return; + } + + // Path changed type so we should clean up all known globals associated with the type + RemoveGlobal(path); + } + + RegisterPath(path, type, recursionCounter); + + // List will need to be sorted on next access + _globalsSorted = false; + } + + private void RegisterPath(string path, Type type, int recursionCounter, LuaGlobalEntry entry = null) + { + // If the type is a global method, list it directly + if (type == typeof(LuaFunction)) + { + RegisterLuaFunction(path, entry); + } + // 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 < MaximumRecursion) + { + RegisterClassOrInterface(path, type, recursionCounter, entry); + } + else + { + RegisterPrimitive(path, entry); + } + } + + private void RegisterLuaFunction(string path, LuaGlobalEntry entry = null) + { + // Format for easy method invocation + _globals.Add(path + "("); + + if (entry != null) + { + entry.linkedGlobals.Add(path); + } + } + + private void RegisterPrimitive(string path, LuaGlobalEntry entry = null) + { + _globals.Add(path); + if (entry != null) + { + entry.linkedGlobals.Add(path); + } + } + + private void RegisterClassOrInterface(string path, Type type, int recursionCounter, LuaGlobalEntry entry = null) + { + if (entry == null) + { + entry = new LuaGlobalEntry(type, path); + _knownTypes.Add(entry); + } + + foreach (var method in type.GetMethods(BindingFlags.Public | BindingFlags.Instance)) + { + string name = method.Name; + if ( + // Check that the LuaHideAttribute and LuaGlobalAttribute were not applied + (!method.GetCustomAttributes(typeof(LuaHideAttribute), false).Any()) && + (!method.GetCustomAttributes(typeof(LuaGlobalAttribute), false).Any()) && + // Exclude some generic .NET methods that wouldn't be very usefull in Lua + name != "GetType" && name != "GetHashCode" && name != "Equals" && + name != "ToString" && name != "Clone" && name != "Dispose" && + name != "GetEnumerator" && name != "CopyTo" && + !name.StartsWith("get_", StringComparison.Ordinal) && + !name.StartsWith("set_", StringComparison.Ordinal) && + !name.StartsWith("add_", StringComparison.Ordinal) && + !name.StartsWith("remove_", StringComparison.Ordinal)) + { + // Format for easy method invocation + string command = path + ":" + name + "("; + + if (method.GetParameters().Length == 0) + command += ")"; + + _globals.Add(command); + entry.linkedGlobals.Add(command); + } + } + + foreach (var field in type.GetFields(BindingFlags.Public | BindingFlags.Instance)) + { + if ( + // Check that the LuaHideAttribute and LuaGlobalAttribute were not applied + (!field.GetCustomAttributes(typeof(LuaHideAttribute), false).Any()) && + (!field.GetCustomAttributes(typeof(LuaGlobalAttribute), false).Any())) + { + // Go into recursion for members + RegisterPath(path + "." + field.Name, field.FieldType, recursionCounter + 1, entry); + } + } + + foreach (var property in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)) + { + if ( + // Check that the LuaHideAttribute and LuaGlobalAttribute were not applied + (!property.GetCustomAttributes(typeof(LuaHideAttribute), false).Any()) && + (!property.GetCustomAttributes(typeof(LuaGlobalAttribute), false).Any()) + // Exclude some generic .NET properties that wouldn't be very useful in Lua + && property.Name != "Item") + { + // Go into recursion for members + RegisterPath(path + "." + property.Name, property.PropertyType, recursionCounter + 1, entry); + } + } + } + } +} + diff --git a/ExternalProjects/NLua/src/LuaHideAttribute.cs b/ExternalProjects/NLua/src/LuaHideAttribute.cs new file mode 100644 index 0000000000..4a351718ba --- /dev/null +++ b/ExternalProjects/NLua/src/LuaHideAttribute.cs @@ -0,0 +1,12 @@ +using System; + +namespace NLua +{ + /// + /// Marks a method, field or property to be hidden from Lua auto-completion + /// + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)] + public sealed class LuaHideAttribute : Attribute + { + } +} \ No newline at end of file diff --git a/ExternalProjects/NLua/src/LuaRegistrationHelper.cs b/ExternalProjects/NLua/src/LuaRegistrationHelper.cs new file mode 100644 index 0000000000..14882ff192 --- /dev/null +++ b/ExternalProjects/NLua/src/LuaRegistrationHelper.cs @@ -0,0 +1,89 @@ +using System; +using System.Reflection; +using System.Diagnostics.CodeAnalysis; + +namespace NLua +{ + public static class LuaRegistrationHelper + { + /// + /// Registers all public instance methods in an object tagged with as Lua global functions + /// + /// The Lua VM to add the methods to + /// The object to get the methods from + public static void TaggedInstanceMethods(Lua lua, object o) + { + if (lua == null) + throw new ArgumentNullException(nameof(lua)); + + if (o == null) + throw new ArgumentNullException(nameof(o)); + + foreach (var method in o.GetType().GetMethods(BindingFlags.Instance | BindingFlags.Public)) + { + foreach (LuaGlobalAttribute attribute in method.GetCustomAttributes(typeof(LuaGlobalAttribute), true)) + { + if (string.IsNullOrEmpty(attribute.Name)) + lua.RegisterFunction(method.Name, o, method); // CLR name + else + lua.RegisterFunction(attribute.Name, o, method); // Custom name + } + } + } + + /// + /// Registers all public static methods in a class tagged with as Lua global functions + /// + /// The Lua VM to add the methods to + /// The class type to get the methods from + public static void TaggedStaticMethods(Lua lua, Type type) + { + if (lua == null) + throw new ArgumentNullException(nameof(lua)); + + if (type == null) + throw new ArgumentNullException(nameof(type)); + + if (!type.IsClass) + throw new ArgumentException("The type must be a class!", nameof(type)); + + foreach (var method in type.GetMethods(BindingFlags.Static | BindingFlags.Public)) + { + foreach (LuaGlobalAttribute attribute in method.GetCustomAttributes(typeof(LuaGlobalAttribute), false)) + { + if (string.IsNullOrEmpty(attribute.Name)) + lua.RegisterFunction(method.Name, null, method); // CLR name + else + lua.RegisterFunction(attribute.Name, null, method); // Custom name + } + } + } + + /// + /// Registers an enumeration's values for usage as a Lua variable table + /// + /// The enum type to register + /// The Lua VM to add the enum to + [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "The type parameter is used to select an enum type")] + public static void Enumeration(Lua lua) + { + if (lua == null) + throw new ArgumentNullException(nameof(lua)); + + Type type = typeof(T); + + if (!type.IsEnum) + throw new InvalidOperationException($"The type must be an enumeration!"); + + string[] names = Enum.GetNames(type); + var values = (T[])Enum.GetValues(type); + lua.NewTable(type.Name); + + for (int i = 0; i < names.Length; i++) + { + string path = type.Name + "." + names[i]; + lua.SetObjectToPath(path, values[i]); + } + } + } +} diff --git a/ExternalProjects/NLua/src/LuaTable.cs b/ExternalProjects/NLua/src/LuaTable.cs new file mode 100644 index 0000000000..c43421801a --- /dev/null +++ b/ExternalProjects/NLua/src/LuaTable.cs @@ -0,0 +1,115 @@ +using System; +using System.Collections; + +using NLua.Extensions; + +namespace NLua +{ + public class LuaTable : LuaBase + { + public LuaTable(int reference, Lua interpreter): base(reference, interpreter) + { + } + + /// + /// Indexer for string fields of the table + /// + public object this[string field] { + get + { + Lua lua; + if (!TryGet(out lua)) + return null; + return lua.GetObject(_Reference, field); + } + set + { + Lua lua; + if (!TryGet(out lua)) + return; + lua.SetObject(_Reference, field, value); + } + } + + /// + /// Indexer for numeric fields of the table + /// + public object this[object field] { + get + { + Lua lua; + if (!TryGet(out lua)) + return null; + + return lua.GetObject(_Reference, field); + } + set + { + Lua lua; + if (!TryGet(out lua)) + return; + + lua.SetObject(_Reference, field, value); + } + } + + public IDictionaryEnumerator GetEnumerator() + { + Lua lua; + if (!TryGet(out lua)) + return null; + + return lua.GetTableDict(this).GetEnumerator(); + } + + public ICollection Keys + { + get + { + Lua lua; + if (!TryGet(out lua)) + return null; + + return lua.GetTableDict(this).Keys; + } + } + + public ICollection Values + { + get + { + Lua lua; + if (!TryGet(out lua)) + return Array.Empty(); + + return lua.GetTableDict(this).Values; + } + } + + /// + /// Gets an string fields of a table ignoring its metatable, + /// if it exists + /// + internal object RawGet(string field) + { + Lua lua; + if (!TryGet(out lua)) + return null; + + return lua.RawGetObject(_Reference, field); + } + + /// + /// Pushes this table into the Lua stack + /// + internal void Push(LuaState luaState) + { + luaState.GetRef(_Reference); + } + + public override string ToString() + { + return "table"; + } + } +} diff --git a/ExternalProjects/NLua/src/LuaThread.cs b/ExternalProjects/NLua/src/LuaThread.cs new file mode 100644 index 0000000000..e82c9a91ab --- /dev/null +++ b/ExternalProjects/NLua/src/LuaThread.cs @@ -0,0 +1,177 @@ +using System; + +using NLua.Exceptions; +using NLua.Extensions; + +namespace NLua +{ + public class LuaThread : LuaBase, IEquatable, IEquatable, IEquatable + { + private LuaState _luaState; + private ObjectTranslator _translator; + + public LuaState State => _luaState; + + /// + /// Get the main thread object + /// + public LuaThread MainThread + { + get + { + LuaState mainThread = _luaState.MainThread; + int oldTop = mainThread.GetTop(); + mainThread.PushThread(); + object returnValue = _translator.GetObject(mainThread, -1); + + mainThread.SetTop(oldTop); + return (LuaThread)returnValue; + } + } + + public LuaThread(int reference, Lua interpreter): base(reference, interpreter) + { + _luaState = interpreter.GetThreadState(reference); + _translator = interpreter.Translator; + } + + /* + * Resumes this thread + */ + public LuaStatus Resume() + { + // We leave nothing on the stack if we error + var oldMainTop = _luaState.MainThread.GetTop(); + var oldCoTop = _luaState.GetTop(); + + LuaStatus ret = _luaState.Resume(null, 0); + + if (ret == LuaStatus.OK || ret == LuaStatus.Yield) + { + return ret; + } + + object coErr = _translator.GetObject(_luaState, -1); + object mainErr = _translator.GetObject(_luaState.MainThread, -1); + _luaState.SetTop(oldCoTop); + _luaState.MainThread.SetTop(oldMainTop); + + if (coErr is LuaScriptException coLuaEx) + { + throw coLuaEx; + } + + if (mainErr is LuaScriptException mainLuaEx) + { + throw mainLuaEx; + } + + if (coErr != null) + { + throw new LuaScriptException(coErr.ToString(), string.Empty); + } + + throw new LuaScriptException($"Unknown Lua Error (status = {ret})", string.Empty); + } + + /// + /// Yields this thread + /// + public void Yield() + { + _luaState.Yield(0); + } + + /// + /// Resets this thread, cleaning its call stack and closing all pending to-be-closed variables. + /// + public int Reset() + { + int oldTop = _luaState.GetTop(); + + int statusCode = _luaState.ResetThread(); /* close its tbc variables */ + + _luaState.SetTop(oldTop); + return statusCode; + } + + public void XMove(LuaState to, object val, int index = 1) + { + int oldTop = _luaState.GetTop(); + + _translator.Push(_luaState, val); + _luaState.XMove(to, index); + + _luaState.SetTop(oldTop); + } + + public void XMove(Lua to, object val, int index = 1) + { + int oldTop = _luaState.GetTop(); + + _translator.Push(_luaState, val); + _luaState.XMove(to.State, index); + + _luaState.SetTop(oldTop); + } + + public void XMove(LuaThread thread, object val, int index = 1) + { + int oldTop = _luaState.GetTop(); + + _translator.Push(_luaState, val); + _luaState.XMove(thread.State, index); + + _luaState.SetTop(oldTop); + } + + /// + /// Pushes this thread into the Lua stack + /// + internal void Push(LuaState luaState) + { + luaState.GetRef(_Reference); + } + + public override string ToString() + { + return "thread"; + } + + public override bool Equals(object obj) + { + if (obj is LuaThread thread) + return this.State == thread.State; + else if (obj is Lua interpreter) + return this.State == interpreter.State; + else if (obj is LuaState state) + return this.State == state; + return base.Equals(obj); + } + + public override int GetHashCode() + { + return base.GetHashCode(); + } + + public bool Equals(LuaThread other) => this.State == other.State; + public bool Equals(LuaState other) => this.State == other; + public bool Equals(Lua other) => this.State == other.State; + + public static explicit operator LuaState(LuaThread thread) => thread.State; + public static explicit operator LuaThread(Lua interpreter) => interpreter.Thread; + + public static bool operator ==(LuaThread threadA, LuaThread threadB) => threadA.State == threadB.State; + public static bool operator !=(LuaThread threadA, LuaThread threadB) => threadA.State != threadB.State; + + public static bool operator ==(LuaThread thread, LuaState state) => thread.State == state; + public static bool operator !=(LuaThread thread, LuaState state) => thread.State != state; + public static bool operator ==(LuaState state, LuaThread thread) => state == thread.State; + public static bool operator !=(LuaState state, LuaThread thread) => state != thread.State; + + public static bool operator ==(LuaThread thread, Lua interpreter) => thread.State == interpreter.State; + public static bool operator !=(LuaThread thread, Lua interpreter) => thread.State != interpreter.State; + public static bool operator ==(Lua interpreter, LuaThread thread) => interpreter.State == thread.State; + public static bool operator !=(Lua interpreter, LuaThread thread) => interpreter.State != thread.State; + } +} diff --git a/ExternalProjects/NLua/src/LuaUserData.cs b/ExternalProjects/NLua/src/LuaUserData.cs new file mode 100644 index 0000000000..463bd56dcb --- /dev/null +++ b/ExternalProjects/NLua/src/LuaUserData.cs @@ -0,0 +1,81 @@ +using NLua.Extensions; + +namespace NLua +{ + public class LuaUserData : LuaBase + { + public LuaUserData(int reference, Lua interpreter):base(reference, interpreter) + { + } + + /// + /// Indexer for string fields of the userdata + /// + public object this[string field] { + get + { + Lua lua; + if (!TryGet(out lua)) + return null; + + return lua.GetObject(_Reference, field); + } + set + { + Lua lua; + if (!TryGet(out lua)) + return; + + lua.SetObject(_Reference, field, value); + } + } + + /// + /// Indexer for numeric fields of the userdata + /// + public object this[object field] { + get + { + Lua lua; + if (!TryGet(out lua)) + return null; + + return lua.GetObject(_Reference, field); + } + set + { + Lua lua; + if (!TryGet(out lua)) + return; + + lua.SetObject(_Reference, field, value); + } + } + + /// + /// Calls the userdata and returns its return values inside + /// an array + /// + public object[] Call(params object[] args) + { + Lua lua; + if (!TryGet(out lua)) + return null; + + return lua.CallFunction(this, args); + } + + /// + /// Pushes this userdata into the Lua stack + /// + internal void Push(LuaState luaState) + { + luaState.GetRef(_Reference); + } + + public override string ToString() + { + return "userdata"; + } + } +} diff --git a/ExternalProjects/NLua/src/Metatables.cs b/ExternalProjects/NLua/src/Metatables.cs new file mode 100644 index 0000000000..214e5e1feb --- /dev/null +++ b/ExternalProjects/NLua/src/Metatables.cs @@ -0,0 +1,1574 @@ +using System; +using System.Linq; +using System.Reflection; +using System.Diagnostics; +using System.Collections.Generic; + +using NLua.Method; +using NLua.Extensions; + +using NLua.Exceptions; + +namespace NLua +{ + public class MetaFunctions + { + public static readonly LuaNativeFunction GcFunction = CollectObject; + public static readonly LuaNativeFunction IndexFunction = GetMethod; + public static readonly LuaNativeFunction NewIndexFunction = SetFieldOrProperty; + public static readonly LuaNativeFunction BaseIndexFunction = GetBaseMethod; + public static readonly LuaNativeFunction ClassIndexFunction = GetClassMethod; + public static readonly LuaNativeFunction ClassNewIndexFunction = SetClassFieldOrProperty; + public static readonly LuaNativeFunction ExecuteDelegateFunction = RunFunctionDelegate; + public static readonly LuaNativeFunction CallConstructorFunction = CallConstructor; + public static readonly LuaNativeFunction ToStringFunction = ToStringLua; + public static readonly LuaNativeFunction CallDelegateFunction = CallDelegate; + + public static readonly LuaNativeFunction AddFunction = AddLua; + public static readonly LuaNativeFunction SubtractFunction = SubtractLua; + public static readonly LuaNativeFunction MultiplyFunction = MultiplyLua; + public static readonly LuaNativeFunction DivisionFunction = DivideLua; + public static readonly LuaNativeFunction ModulosFunction = ModLua; + public static readonly LuaNativeFunction UnaryNegationFunction = UnaryNegationLua; + public static readonly LuaNativeFunction EqualFunction = EqualLua; + public static readonly LuaNativeFunction LessThanFunction = LessThanLua; + public static readonly LuaNativeFunction LessThanOrEqualFunction = LessThanOrEqualLua; + + internal readonly Dictionary> _memberCache = new Dictionary>(); + internal readonly ObjectTranslator _translator; + + /// + /// __index metafunction for CLR objects. Implemented in Lua. + /// + public const string LuaIndexFunction = @"local a={}local function b(c,d)local e=getmetatable(c)local f=e.cache[d]if f~=nil then if f==a then return nil end;return f else local g,h=get_object_member(c,d)if h then if g==nil then e.cache[d]=a else e.cache[d]=g end end;return g end end;return b"; + //@"local fakenil = {} + // local function index(obj, name) + // local meta = getmetatable(obj) + // local cached = meta.cache[name] + + // if cached ~= nil then + // if cached == fakenil then + // return nil + // end + // return cached + + // else + // local value, isCached = get_object_member(obj, name) + // if isCached then + // if value == nil then + // meta.cache[name] = fakenil + // else + // meta.cache[name] = value + // end + // end + // return value + // end + // end + + // return index"; + + public MetaFunctions(ObjectTranslator translator) + { + _translator = translator; + } + + /// + /// __call metafunction of CLR delegates, retrieves and calls the delegate. + /// + private static int RunFunctionDelegate(IntPtr luaState) + { + var state = LuaState.FromIntPtr(luaState); + var translator = ObjectTranslatorPool.Instance.Find(state); + var func = (LuaNativeFunction)translator.GetRawNetObject(state, 1); + if (func == null) + return state.Error(); + + state.Remove(1); + int result = func(luaState); + var exception = translator.GetObject(state, -1) as LuaScriptException; + + if (exception != null) + return state.Error(); + + if (state != state.MainThread) + { + if (translator.GetObject(state.MainThread, -1) is LuaScriptException) + return state.Error(); + } + + return result; + } + + /// + /// __gc metafunction of CLR objects. + /// + private static int CollectObject(IntPtr state) + { + var luaState = LuaState.FromIntPtr(state); + var translator = ObjectTranslatorPool.Instance.Find(luaState); + return CollectObject(luaState, translator); + } + + private static int CollectObject(LuaState luaState, ObjectTranslator translator) + { + int udata = luaState.RawNetObj(1); + + if (udata != -1) + translator.CollectObject(udata); + + return 0; + } + + /// + /// __tostring metafunction of CLR objects. + /// + private static int ToStringLua(IntPtr state) + { + var luaState = LuaState.FromIntPtr(state); + var translator = ObjectTranslatorPool.Instance.Find(luaState); + return ToStringLua(luaState, translator); + } + + private static int ToStringLua(LuaState luaState, ObjectTranslator translator) + { + object obj = translator.GetRawNetObject(luaState, 1); + + if (obj != null) + translator.Push(luaState, obj + ": " + obj.GetHashCode()); + else + luaState.PushNil(); + + return 1; + } + + /// + /// __add metafunction of CLR objects. + /// + internal static int AddLua(IntPtr luaState) + { + var state = LuaState.FromIntPtr(luaState); + var translator = ObjectTranslatorPool.Instance.Find(state); + int result = MatchOperator(state, "op_Addition", translator); + var exception = translator.GetObject(state, -1) as LuaScriptException; + + if (exception != null) + return state.Error(); + return result; + } + + /// + /// __sub metafunction of CLR objects. + /// + internal static int SubtractLua(IntPtr luaState) + { + var state = LuaState.FromIntPtr(luaState); + var translator = ObjectTranslatorPool.Instance.Find(state); + int result = MatchOperator(state, "op_Subtraction", translator); + var exception = translator.GetObject(state, -1) as LuaScriptException; + + if (exception != null) + return state.Error(); + return result; + } + + /// + /// __mul metafunction of CLR objects. + /// + internal static int MultiplyLua(IntPtr luaState) + { + var state = LuaState.FromIntPtr(luaState); + var translator = ObjectTranslatorPool.Instance.Find(state); + int result = MatchOperator(state, "op_Multiply", translator); + var exception = translator.GetObject(state, -1) as LuaScriptException; + + if (exception != null) + return state.Error(); + return result; + } + + /// + /// __div metafunction of CLR objects. + /// + internal static int DivideLua(IntPtr luaState) + { + var state = LuaState.FromIntPtr(luaState); + var translator = ObjectTranslatorPool.Instance.Find(state); + int result = MatchOperator(state, "op_Division", translator); + var exception = translator.GetObject(state, -1) as LuaScriptException; + + if (exception != null) + return state.Error(); + return result; + } + + /// + /// __mod metafunction of CLR objects. + /// + internal static int ModLua(IntPtr luaState) + { + var state = LuaState.FromIntPtr(luaState); + var translator = ObjectTranslatorPool.Instance.Find(state); + int result = MatchOperator(state, "op_Modulus", translator); + var exception = translator.GetObject(state, -1) as LuaScriptException; + + if (exception != null) + return state.Error(); + return result; + } + + /// + /// __unm metafunction of CLR objects. + /// + internal static int UnaryNegationLua(IntPtr luaState) + { + var state = LuaState.FromIntPtr(luaState); + var translator = ObjectTranslatorPool.Instance.Find(state); + int result = UnaryNegationLua(state, translator); + var exception = translator.GetObject(state, -1) as LuaScriptException; + + if (exception != null) + return state.Error(); + return result; + } + + internal static int UnaryNegationLua(LuaState luaState, ObjectTranslator translator) //-V3009 + { + object obj1 = translator.GetRawNetObject(luaState, 1); + + if (obj1 == null) + { + translator.ThrowError(luaState, "Cannot negate a nil object"); + return 1; + } + + Type type = obj1.GetType(); + MethodInfo opUnaryNegation = type.GetMethod("op_UnaryNegation"); + + if (opUnaryNegation == null) + { + translator.ThrowError(luaState, "Cannot negate object (" + type.Name + " does not overload the operator -)"); + return 1; + } + obj1 = opUnaryNegation.Invoke(obj1, new[] { obj1 }); + translator.Push(luaState, obj1); + return 1; + } + + /// + /// __eq metafunction of CLR objects. + /// + internal static int EqualLua(IntPtr luaState) + { + var state = LuaState.FromIntPtr(luaState); + var translator = ObjectTranslatorPool.Instance.Find(state); + int result = MatchOperator(state, "op_Equality", translator); + var exception = translator.GetObject(state, -1) as LuaScriptException; + + if (exception != null) + return state.Error(); + return result; + } + + /// + /// __lt metafunction of CLR objects. + /// + internal static int LessThanLua(IntPtr luaState) + { + var state = LuaState.FromIntPtr(luaState); + var translator = ObjectTranslatorPool.Instance.Find(state); + int result = MatchOperator(state, "op_LessThan", translator); + var exception = translator.GetObject(state, -1) as LuaScriptException; + + if (exception != null) + return state.Error(); + return result; + } + + /// + /// __le metafunction of CLR objects. + /// + internal static int LessThanOrEqualLua(IntPtr luaState) + { + var state = LuaState.FromIntPtr(luaState); + var translator = ObjectTranslatorPool.Instance.Find(state); + int result = MatchOperator(state, "op_LessThanOrEqual", translator); + var exception = translator.GetObject(state, -1) as LuaScriptException; + + if (exception != null) + return state.Error(); + return result; + } + + /// + /// Debug tool to dump the lua stack + /// FIXME, move somewhere else + /// + public static void DumpStack(ObjectTranslator translator, LuaState luaState) + { + int depth = luaState.GetTop(); + + Debug.WriteLine("lua stack depth: {0}", depth); + + for (int i = 1; i <= depth; i++) + { + var type = luaState.Type(i); + // we dump stacks when deep in calls, calling typename while the stack is in flux can fail sometimes, so manually check for key types + string typestr = (type == LuaType.Table) ? "table" : luaState.TypeName(type); + string strrep = luaState.ToString(i, false); + + if (type == LuaType.UserData) + { + object obj = translator.GetRawNetObject(luaState, i); + + strrep = obj == null ? "(null)" : obj.ToString(); + } + + Debug.WriteLine("{0}: ({1}) {2}", i, typestr, strrep); + } + } + + /// + /// Called by the __index metafunction of CLR objects in case the + /// method is not cached or it is a field/property/event. + /// Receives the object and the member name as arguments and returns + /// either the value of the member or a delegate to call it. + /// If the member does not exist returns nil. + /// + private static int GetMethod(IntPtr state) + { + var luaState = LuaState.FromIntPtr(state); + var translator = ObjectTranslatorPool.Instance.Find(luaState); + var instance = translator.MetaFunctionsInstance; + int result = instance.GetMethodInternal(luaState); + var exception = translator.GetObject(luaState, -1) as LuaScriptException; + + if (exception != null) + return luaState.Error(); + return result; + } + + private int GetMethodInternal(LuaState luaState) + { + object obj = _translator.GetRawNetObject(luaState, 1); + + if (obj == null) + { + _translator.ThrowError(luaState, "Trying to index an invalid object reference"); + return 1; + } + + object index = _translator.GetObject(luaState, 2); + string methodName = index as string; // will be null if not a string arg + var objType = obj.GetType(); + var proxyType = new ProxyType(objType); + + // Handle the most common case, looking up the method by name. + // CP: This will fail when using indexers and attempting to get a value with the same name as a property of the object, + // ie: xmlelement['item'] <- item is a property of xmlelement + + if (!string.IsNullOrEmpty(methodName) && IsMemberPresent(proxyType, methodName)) + return GetMember(luaState, proxyType, obj, methodName, BindingFlags.Instance); + + // Try to access by array if the type is right and index is an int (lua numbers always come across as double) + if (TryAccessByArray(luaState, objType, obj, index)) + return 1; + + int fallback = GetMethodFallback(luaState, objType, obj, methodName); + if (fallback != 0) + return fallback; + + if (!string.IsNullOrEmpty(methodName) || index != null) + { + if (string.IsNullOrEmpty(methodName)) + methodName = index.ToString(); + + return PushInvalidMethodCall(luaState, objType, methodName); + } + + luaState.PushBoolean(false); + return 2; + } + + private int PushInvalidMethodCall(LuaState luaState, Type type, string name) + { + SetMemberCache(type, name, null); + + _translator.Push(luaState, null); + _translator.Push(luaState, false); + return 2; + } + + private bool TryAccessByArray(LuaState luaState, + Type objType, + object obj, + object index) + { + if (!objType.IsArray) + return false; + + int intIndex = -1; + if (index is long l) + intIndex = (int)l; + else if (index is double d) + intIndex = (int)d; + + if (intIndex == -1) + return false; + + Type type = objType.UnderlyingSystemType; + + if (type == typeof(long[])) + { + long[] arr = (long[])obj; + _translator.Push(luaState, arr[intIndex]); + return true; + } + if (type == typeof(float[])) + { + float[] arr = (float[])obj; + _translator.Push(luaState, arr[intIndex]); + return true; + } + if (type == typeof(double[])) + { + double[] arr = (double[])obj; + _translator.Push(luaState, arr[intIndex]); + return true; + } + if (type == typeof(int[])) + { + int[] arr = (int[])obj; + _translator.Push(luaState, arr[intIndex]); + return true; + } + if (type == typeof(byte[])) + { + byte[] arr = (byte[])obj; + _translator.Push(luaState, arr[intIndex]); + return true; + } + if (type == typeof(short[])) + { + short[] arr = (short[])obj; + _translator.Push(luaState, arr[intIndex]); + return true; + } + if (type == typeof(ushort[])) + { + ushort[] arr = (ushort[])obj; + _translator.Push(luaState, arr[intIndex]); + return true; + } + if (type == typeof(ulong[])) + { + ulong[] arr = (ulong[])obj; + _translator.Push(luaState, arr[intIndex]); + return true; + } + if (type == typeof(uint[])) + { + uint[] arr = (uint[])obj; + _translator.Push(luaState, arr[intIndex]); + return true; + } + if (type == typeof(sbyte[])) + { + sbyte[] arr = (sbyte[])obj; + _translator.Push(luaState, arr[intIndex]); + return true; + } + + var array = (Array)obj; + object element = array.GetValue(intIndex); + _translator.Push(luaState, element); + return true; + } + + private int GetMethodFallback + (LuaState luaState, + Type objType, + object obj, + string methodName) + { + object method; + if (!string.IsNullOrEmpty(methodName) && TryGetExtensionMethod(objType, methodName, out method)) + { + return PushExtensionMethod(luaState, objType, obj, methodName, method); + } + // Try to use get_Item to index into this .net object + MethodInfo[] methods = objType.GetMethods(); + + int res = TryIndexMethods(luaState, methods, obj); + if (res != 0) + return res; + + // Fallback to GetRuntimeMethods + methods = objType.GetRuntimeMethods().ToArray(); + + res = TryIndexMethods(luaState, methods, obj); + if (res != 0) + return res; + + res = TryGetValueForKeyMethods(luaState, methods, obj); + if (res != 0) + return res; + + // Try find explicity interface implementation + MethodInfo explicitInterfaceMethod = objType.GetMethods(BindingFlags.NonPublic | BindingFlags.Instance). + FirstOrDefault(m => m.Name == methodName && m.IsPrivate && m.IsVirtual && m.IsFinal); + + if (explicitInterfaceMethod != null) + { + var proxyType = new ProxyType(objType); + var methodWrapper = new LuaMethodWrapper(_translator, obj, proxyType, explicitInterfaceMethod); + var invokeDelegate = new LuaNativeFunction(methodWrapper.InvokeFunction); + + SetMemberCache(proxyType, methodName, invokeDelegate); + + _translator.PushFunction(luaState, invokeDelegate); + _translator.Push(luaState, true); + return 2; + } + + return 0; + } + + private int TryGetValueForKeyMethods(LuaState luaState, MethodInfo[] methods, object obj) + { + foreach (MethodInfo methodInfo in methods) + { + if (methodInfo.Name != "TryGetValueForKey") + continue; + + // Check if the signature matches the input + if (methodInfo.GetParameters().Length != 2) + continue; + + ParameterInfo[] actualParams = methodInfo.GetParameters(); + + // Get the index in a form acceptable to the getter + object index = _translator.GetAsType(luaState, 2, actualParams[0].ParameterType); + + // If the index type and the parameter doesn't match, just skip it + if (index == null) + break; + + object[] args = new object[2]; + + // Just call the indexer - if out of bounds an exception will happen + args[0] = index; + + try + { + bool found = (bool)methodInfo.Invoke(obj, args); + + if (!found) + { + _translator.ThrowError(luaState, "key not found: " + index); + return 1; + } + + _translator.Push(luaState, args[1]); + return 1; + } + catch (TargetInvocationException e) + { + // Provide a more readable description for the common case of key not found + if (e.InnerException is KeyNotFoundException) + _translator.ThrowError(luaState, "key '" + index + "' not found "); + else + _translator.ThrowError(luaState, "exception indexing '" + index + "' " + e.Message); + + return 1; + } + } + return 0; + } + + private int TryIndexMethods(LuaState luaState, MethodInfo [] methods, object obj) + { + foreach (MethodInfo methodInfo in methods) + { + if (methodInfo.Name != "get_Item") + continue; + + // Check if the signature matches the input + if (methodInfo.GetParameters().Length != 1) + continue; + + ParameterInfo[] actualParams = methodInfo.GetParameters(); + + // Get the index in a form acceptable to the getter + object index = _translator.GetAsType(luaState, 2, actualParams[0].ParameterType); + + // If the index type and the parameter doesn't match, just skip it + if (index == null) + continue; + + object[] args = new object[1]; + + // Just call the indexer - if out of bounds an exception will happen + args[0] = index; + + try + { + object result = methodInfo.Invoke(obj, args); + _translator.Push(luaState, result); + return 1; + } + catch (TargetInvocationException e) + { + // Provide a more readable description for the common case of key not found + if (e.InnerException is KeyNotFoundException) + _translator.ThrowError(luaState, "key '" + index + "' not found "); + else + _translator.ThrowError(luaState, "exception indexing '" + index + "' " + e.Message); + + return 1; + } + } + return 0; + } + + /// + /// __index metafunction of base classes (the base field of Lua tables). + /// Adds a prefix to the method name to call the base version of the method. + /// + private static int GetBaseMethod(IntPtr state) + { + var luaState = LuaState.FromIntPtr(state); + var translator = ObjectTranslatorPool.Instance.Find(luaState); + var instance = translator.MetaFunctionsInstance; + int result = instance.GetBaseMethodInternal(luaState); + var exception = translator.GetObject(luaState, -1) as LuaScriptException; + + if (exception != null) + return luaState.Error(); + return result; + } + + private int GetBaseMethodInternal(LuaState luaState) + { + object obj = _translator.GetRawNetObject(luaState, 1); + + if (obj == null) + { + _translator.ThrowError(luaState, "Trying to index an invalid object reference"); + return 1; + } + + string methodName = luaState.ToString(2, false); + + if (string.IsNullOrEmpty(methodName)) + { + luaState.PushNil(); + luaState.PushBoolean(false); + return 2; + } + + GetMember(luaState, new ProxyType(obj.GetType()), obj, "__luaInterface_base_" + methodName, BindingFlags.Instance); + luaState.SetTop(-2); + + if (luaState.Type(-1) == LuaType.Nil) + { + luaState.SetTop(-2); + return GetMember(luaState, new ProxyType(obj.GetType()), obj, methodName, BindingFlags.Instance); + } + + luaState.PushBoolean(false); + return 2; + } + + /// + /// Does this method exist as either an instance or static? + /// + /// + /// + /// + internal bool IsMemberPresent(ProxyType objType, string methodName) + { + object cachedMember = CheckMemberCache(objType, methodName); + + if (cachedMember != null) + return true; + + var members = objType.GetMember(methodName, BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public); + return members.Length > 0; + } + + internal bool TryGetExtensionMethod(Type type, string name, out object method) + { + object cachedMember = CheckMemberCache(type, name); + + if (cachedMember != null) + { + method = cachedMember; + return true; + } + + MethodInfo methodInfo; + bool found = _translator.TryGetExtensionMethod(type, name, out methodInfo); + method = methodInfo; + return found; + } + + internal int PushExtensionMethod(LuaState luaState, Type type, object obj, string name, object method) + { + var cachedMember = method as LuaNativeFunction; + + if (cachedMember != null) + { + _translator.PushFunction(luaState, cachedMember); + _translator.Push(luaState, true); + return 2; + } + + var methodInfo = (MethodInfo)method; + var methodWrapper = new LuaMethodWrapper(_translator, obj, new ProxyType(type), methodInfo); + var invokeDelegate = new LuaNativeFunction(methodWrapper.InvokeFunction); + + SetMemberCache(type, name, invokeDelegate); + + _translator.PushFunction(luaState, invokeDelegate); + _translator.Push(luaState, true); + return 2; + } + + /// + /// Pushes the value of a member or a delegate to call it, depending on the type of + /// the member. Works with static or instance members. + /// Uses reflection to find members, and stores the reflected MemberInfo object in + /// a cache (indexed by the type of the object and the name of the member). + /// + internal int GetMember(LuaState luaState, ProxyType objType, object obj, string methodName, BindingFlags bindingType) + { + bool implicitStatic = false; + MemberInfo member = null; + object cachedMember = CheckMemberCache(objType, methodName); + + if (cachedMember is LuaNativeFunction) + { + _translator.PushFunction(luaState, (LuaNativeFunction)cachedMember); + _translator.Push(luaState, true); + return 2; + } + if (cachedMember != null) + member = (MemberInfo)cachedMember; + else + { + var members = objType.GetMember(methodName, bindingType | BindingFlags.Public); + + if (members.Length > 0) + member = members[0]; + else + { + // If we can't find any suitable instance members, try to find them as statics - but we only want to allow implicit static + members = objType.GetMember(methodName, bindingType | BindingFlags.Static | BindingFlags.Public); + + if (members.Length > 0) + { + member = members[0]; + implicitStatic = true; + } + } + } + + if (member != null) + { + if (member.MemberType == MemberTypes.Field) + { + var field = (FieldInfo)member; + + if (cachedMember == null) + SetMemberCache(objType, methodName, member); + + try + { + var value = field.GetValue(obj); + _translator.Push(luaState, value); + } + catch + { + Debug.WriteLine("[Exception] Fail to get field value"); + luaState.PushNil(); + } + } + else if (member.MemberType == MemberTypes.Property) + { + var property = (PropertyInfo)member; + if (cachedMember == null) + SetMemberCache(objType, methodName, member); + + try + { + object value = property.GetValue(obj, null); + _translator.Push(luaState, value); + } + catch (ArgumentException) + { + // If we can't find the getter in our class, recurse up to the base class and see + // if they can help. + if (objType.UnderlyingSystemType != typeof(object)) + return GetMember(luaState, new ProxyType(objType.UnderlyingSystemType.BaseType), obj, methodName, bindingType); + luaState.PushNil(); + } + catch (TargetInvocationException e) + { // Convert this exception into a Lua error + ThrowError(luaState, e); + luaState.PushNil(); + } + } + else if (member.MemberType == MemberTypes.Event) + { + var eventInfo = (EventInfo)member; + if (cachedMember == null) + SetMemberCache(objType, methodName, member); + + _translator.Push(luaState, new RegisterEventHandler(_translator.PendingEvents, obj, eventInfo)); + } + else if (!implicitStatic) + { + if (member.MemberType == MemberTypes.NestedType && member.DeclaringType != null) + { + if (cachedMember == null) + SetMemberCache(objType, methodName, member); + + // Find the name of our class + string name = member.Name; + Type decType = member.DeclaringType; + + // Build a new long name and try to find the type by name + string longName = decType.FullName + "+" + name; + var nestedType = _translator.FindType(longName); + _translator.PushType(luaState, nestedType); + } + else + { + // Member type must be 'method' + var methodWrapper = new LuaMethodWrapper(_translator, objType, methodName, bindingType); + var wrapper = methodWrapper.InvokeFunction; + + if (cachedMember == null) + SetMemberCache(objType, methodName, wrapper); + + _translator.PushFunction(luaState, wrapper); + _translator.Push(luaState, true); + return 2; + } + } + else + { + // If we reach this point we found a static method, but can't use it in this context because the user passed in an instance + _translator.ThrowError(luaState, "Can't pass instance to static method " + methodName); + return 1; + } + } + else + { + if (objType.UnderlyingSystemType != typeof(object)) + return GetMember(luaState, new ProxyType(objType.UnderlyingSystemType.BaseType), obj, methodName, bindingType); + + // We want to throw an exception because merely returning 'nil' in this case + // is not sufficient. valid data members may return nil and therefore there must be some + // way to know the member just doesn't exist. + _translator.ThrowError(luaState, "Unknown member name " + methodName); + return 1; + } + + // Push false because we are NOT returning a function (see luaIndexFunction) + _translator.Push(luaState, false); + return 2; + } + + /// + /// Checks if a MemberInfo object is cached, returning it or null. + /// + internal object CheckMemberCache(Type objType, string memberName) + { + return CheckMemberCache(new ProxyType(objType), memberName); + } + + internal object CheckMemberCache(ProxyType objType, string memberName) + { + Dictionary members; + + if (!_memberCache.TryGetValue(objType, out members)) + return null; + + object memberValue; + + if (members == null || !members.TryGetValue(memberName, out memberValue)) + return null; + + return memberValue; + } + + /// + /// Stores a MemberInfo object in the member cache. + /// + internal void SetMemberCache(Type objType, string memberName, object member) + { + SetMemberCache(new ProxyType(objType), memberName, member); + } + + internal void SetMemberCache(ProxyType objType, string memberName, object member) + { + Dictionary members; + Dictionary memberCacheValue; + + if (_memberCache.TryGetValue(objType, out memberCacheValue)) + { + members = memberCacheValue; + } + else + { + members = new Dictionary(); + _memberCache[objType] = members; + } + + members[memberName] = member; + } + + /// + /// __newindex metafunction of CLR objects. Receives the object, + /// the member name and the value to be stored as arguments. Throws + /// and error if the assignment is invalid. + /// + private static int SetFieldOrProperty(IntPtr state) + { + var luaState = LuaState.FromIntPtr(state); + var translator = ObjectTranslatorPool.Instance.Find(luaState); + var instance = translator.MetaFunctionsInstance; + int result = instance.SetFieldOrPropertyInternal(luaState); + var exception = translator.GetObject(luaState, -1) as LuaScriptException; + + if (exception != null) + return luaState.Error(); + return result; + } + + private int SetFieldOrPropertyInternal(LuaState luaState) + { + object target = _translator.GetRawNetObject(luaState, 1); + + if (target == null) + { + _translator.ThrowError(luaState, "trying to index and invalid object reference"); + return 1; + } + + var type = target.GetType(); + + // First try to look up the parameter as a property name + string detailMessage; + bool didMember = TrySetMember(luaState, new ProxyType(type), target, BindingFlags.Instance, out detailMessage); + + if (didMember) + return 0; // Must have found the property name + + // We didn't find a property name, now see if we can use a [] style this accessor to set array contents + try + { + if (type.IsArray && luaState.IsNumber(2)) + { + int index = (int)luaState.ToNumber(2); + var arr = (Array)target; + object val = _translator.GetAsType(luaState, 3, arr.GetType().GetElementType()); + arr.SetValue(val, index); + } + else + { + // Try to see if we have a this[] accessor + var setter = type.GetMethod("set_Item"); + if (setter != null) + { + var args = setter.GetParameters(); + var valueType = args[1].ParameterType; + + // The new value the user specified + object val = _translator.GetAsType(luaState, 3, valueType); + var indexType = args[0].ParameterType; + object index = _translator.GetAsType(luaState, 2, indexType); + + object[] methodArgs = new object[2]; + + // Just call the indexer - if out of bounds an exception will happen + methodArgs[0] = index; + methodArgs[1] = val; + setter.Invoke(target, methodArgs); + } + else + { + _translator.ThrowError(luaState, detailMessage); // Pass the original message from trySetMember because it is probably best + return 1; + } + } + } + catch (Exception e) + { + ThrowError(luaState, e); + return 1; + } + + return 0; + } + + /// + /// Tries to set a named property or field + /// + /// + /// + /// + /// + /// false if unable to find the named member, true for success + internal bool TrySetMember(LuaState luaState, ProxyType targetType, object target, BindingFlags bindingType, out string detailMessage) + { + detailMessage = null; // No error yet + + // If not already a string just return - we don't want to call tostring - which has the side effect of + // changing the lua typecode to string + // Note: We don't use isstring because the standard lua C isstring considers either strings or numbers to + // be true for isstring. + if (luaState.Type(2) != LuaType.String) + { + detailMessage = "property names must be strings"; + return false; + } + + // We only look up property names by string + string fieldName = luaState.ToString(2, false); + if (string.IsNullOrEmpty(fieldName) || !(char.IsLetter(fieldName[0]) || fieldName[0] == '_')) + { + detailMessage = "Invalid property name"; + return false; + } + + // Find our member via reflection or the cache + var member = (MemberInfo)CheckMemberCache(targetType, fieldName); + if (member == null) + { + var members = targetType.GetMember(fieldName, bindingType | BindingFlags.Public); + + if (members.Length <= 0) + { + detailMessage = "field or property '" + fieldName + "' does not exist"; + return false; + } + + member = members[0]; + SetMemberCache(targetType, fieldName, member); + } + + if (member.MemberType == MemberTypes.Field) + { + var field = (FieldInfo)member; + object val = _translator.GetAsType(luaState, 3, field.FieldType); + + try + { + field.SetValue(target, val); + } + catch (Exception e) + { + detailMessage = "Error setting field: " + e.Message; + return false; + } + + return true; + } + if (member.MemberType == MemberTypes.Property) + { + var property = (PropertyInfo)member; + object val = _translator.GetAsType(luaState, 3, property.PropertyType); + + try + { + property.SetValue(target, val, null); + } + catch (Exception e) + { + detailMessage = "Error setting property: " + e.Message; + return false; + } + + return true; + } + + detailMessage = "'" + fieldName + "' is not a .net field or property"; + return false; + } + + /// + /// Writes to fields or properties, either static or instance. Throws an error + /// if the operation is invalid. + /// + private int SetMember(LuaState luaState, ProxyType targetType, object target, BindingFlags bindingType) + { + string detail; + bool success = TrySetMember(luaState, targetType, target, bindingType, out detail); + + if (!success) + { + _translator.ThrowError(luaState, detail); + return 1; + } + + return 0; + } + + /// + /// Convert a C# exception into a Lua error + /// + /// + /// + /// We try to look into the exception to give the most meaningful description + internal void ThrowError(LuaState luaState, Exception e) + { + // If we got inside a reflection show what really happened + var te = e as TargetInvocationException; + + if (te != null) + e = te.InnerException; + + _translator.ThrowError(luaState, e); + } + + /// + /// __index metafunction of type references, works on static members. + /// + private static int GetClassMethod(IntPtr state) + { + var luaState = LuaState.FromIntPtr(state); + var translator = ObjectTranslatorPool.Instance.Find(luaState); + var instance = translator.MetaFunctionsInstance; + int result = instance.GetClassMethodInternal(luaState); + var exception = translator.GetObject(luaState, -1) as LuaScriptException; + + if (exception != null) + return luaState.Error(); + return result; + } + + private int GetClassMethodInternal(LuaState luaState) + { + var klass = _translator.GetRawNetObject(luaState, 1) as ProxyType; + + if (klass == null) + { + _translator.ThrowError(luaState, "Trying to index an invalid type reference"); + return 1; + } + + if (luaState.IsNumber(2)) + { + int size = (int)luaState.ToNumber(2); + _translator.Push(luaState, Array.CreateInstance(klass.UnderlyingSystemType, size)); + return 1; + } + + string methodName = luaState.ToString(2, false); + + if (string.IsNullOrEmpty(methodName)) + { + luaState.PushNil(); + return 1; + } + return GetMember(luaState, klass, null, methodName, BindingFlags.Static); + } + + /// + /// __newindex function of type references, works on static members. + /// + private static int SetClassFieldOrProperty(IntPtr state) + { + var luaState = LuaState.FromIntPtr(state); + var translator = ObjectTranslatorPool.Instance.Find(luaState); + var instance = translator.MetaFunctionsInstance; + int result = instance.SetClassFieldOrPropertyInternal(luaState); + var exception = translator.GetObject(luaState, -1) as LuaScriptException; + + if (exception != null) + return luaState.Error(); + return result; + } + + private int SetClassFieldOrPropertyInternal(LuaState luaState) + { + var target = _translator.GetRawNetObject(luaState, 1) as ProxyType; + + if (target == null) + { + _translator.ThrowError(luaState, "trying to index an invalid type reference"); + return 1; + } + + return SetMember(luaState, target, null, BindingFlags.Static); + } + + /// + /// __call metafunction of Delegates. + /// + internal static int CallDelegate(IntPtr state) + { + var luaState = LuaState.FromIntPtr(state); + var translator = ObjectTranslatorPool.Instance.Find(luaState); + var instance = translator.MetaFunctionsInstance; + int result = instance.CallDelegateInternal(luaState); + var exception = translator.GetObject(luaState, -1) as LuaScriptException; + + if (exception != null) + return luaState.Error(); + + return result; + } + + internal int CallDelegateInternal(LuaState luaState) + { + var del = _translator.GetRawNetObject(luaState, 1) as Delegate; + + if (del == null) + { + _translator.ThrowError(luaState, "Trying to invoke a not delegate or callable value"); + return 1; + } + + luaState.Remove(1); + + var validDelegate = new MethodCache(); + MethodBase methodDelegate = del.Method; + bool isOk = MatchParameters(luaState, methodDelegate, validDelegate, 0); + + if (isOk) + { + object result; + try + { + if (methodDelegate.IsStatic) + result = methodDelegate.Invoke(null, validDelegate.args); + else + result = methodDelegate.Invoke(del.Target, validDelegate.args); + + _translator.Push(luaState, result); + return 1; + } + catch (TargetInvocationException e) + { + // Failure of method invocation + if (_translator.interpreter.UseTraceback) + e.GetBaseException().Data["Traceback"] = _translator.interpreter.GetDebugTraceback(); + return _translator.Interpreter.SetPendingException(e.GetBaseException()); + } + catch (Exception e) + { + return _translator.Interpreter.SetPendingException(e); + } + } + + _translator.ThrowError(luaState, "Cannot invoke delegate (invalid arguments for " + methodDelegate.Name + ")"); + return 1; + } + + /// + /// __call metafunction of type references. Searches for and calls + /// a constructor for the type. Returns nil if the constructor is not + /// found or if the arguments are invalid. Throws an error if the constructor + /// generates an exception. + /// + private static int CallConstructor(IntPtr state) + { + var luaState = LuaState.FromIntPtr(state); + var translator = ObjectTranslatorPool.Instance.Find(luaState); + var instance = translator.MetaFunctionsInstance; + int result = instance.CallConstructorInternal(luaState); + var exception = translator.GetObject(luaState, -1) as LuaScriptException; + + if (exception != null) + return luaState.Error(); + return result; + } + + private static ConstructorInfo[] ReorderConstructors(ConstructorInfo[] constructors) + { + int len = constructors.Length; + + if (len < 2) + return constructors; + + return constructors. + GroupBy(c => c.GetParameters().Length). + SelectMany(g => g.OrderByDescending(ci => ci.ToString())). + ToArray(); + } + + private int CallConstructorInternal(LuaState luaState) + { + var klass = _translator.GetRawNetObject(luaState, 1) as ProxyType; + + if (klass == null) + { + _translator.ThrowError(luaState, "Trying to call constructor on an invalid type reference"); + return 1; + } + + var validConstructor = new MethodCache(); + + luaState.Remove(1); + ConstructorInfo[] constructors = klass.UnderlyingSystemType.GetConstructors(); + constructors = ReorderConstructors(constructors); + foreach (var constructor in constructors) + { + bool isConstructor = MatchParameters(luaState, constructor, validConstructor, 0); + + if (!isConstructor) + continue; + + try + { + _translator.Push(luaState, constructor.Invoke(validConstructor.args)); + } + catch (TargetInvocationException e) + { + ThrowError(luaState, e); + return 1; + } + catch + { + luaState.PushNil(); + } + return 1; + } + + if (klass.UnderlyingSystemType.IsValueType) + { + int numLuaParams = luaState.GetTop(); + if (numLuaParams == 0) + { + _translator.Push(luaState, Activator.CreateInstance(klass.UnderlyingSystemType)); + return 1; + } + } + + string constructorName = constructors.Length == 0 ? "unknown" : constructors[0].Name; + _translator.ThrowError(luaState, string.Format("{0} does not contain constructor({1}) argument match", + klass.UnderlyingSystemType, constructorName)); + return 1; + } + + internal static bool IsInteger(double x) + { + return Math.Ceiling(x) == x; + } + + internal static object GetTargetObject(LuaState luaState, string operation, ObjectTranslator translator) + { + Type t; + object target = translator.GetRawNetObject(luaState, 1); + if (target != null) + { + t = target.GetType(); + if (t.HasMethod(operation)) + return target; + } + target = translator.GetRawNetObject(luaState, 2); + if (target != null) + { + t = target.GetType(); + if (t.HasMethod(operation)) + return target; + } + return null; + } + + internal static int MatchOperator(LuaState luaState, string operation, ObjectTranslator translator) + { + var validOperator = new MethodCache(); + + object target = GetTargetObject(luaState, operation, translator); + + if (target == null) + { + translator.ThrowError(luaState, "Cannot call " + operation + " on a nil object"); + return 1; + } + + Type type = target.GetType(); + var operators = type.GetMethods(operation, BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static); + + foreach (var op in operators) + { + bool isOk = translator.MatchParameters(luaState, op, validOperator, 0); + + if (!isOk) + continue; + + object result; + if (op.IsStatic) + result = op.Invoke(null, validOperator.args); + else + result = op.Invoke(target, validOperator.args); + translator.Push(luaState, result); + return 1; + } + + translator.ThrowError(luaState, "Cannot call (" + operation + ") on object type " + type.Name); + return 1; + } + + internal Array TableToArray(LuaState luaState, ExtractValue extractValue, Type paramArrayType, ref int startIndex, int count) + { + Array paramArray; + + if (count == 0) + return Array.CreateInstance(paramArrayType, 0); + + var luaParamValue = extractValue(luaState, startIndex); + startIndex++; + + paramArray = Array.CreateInstance(paramArrayType, count); + + paramArray.SetValue(luaParamValue, 0); + + for (int i = 1; i < count; i++) + { + var value = extractValue(luaState, startIndex); + paramArray.SetValue(value, i); + startIndex++; + } + + return paramArray; + + } + + /// + /// Matches a method against its arguments in the Lua stack. Returns + /// if the match was successful. It it was also returns the information + /// necessary to invoke the method. + /// + internal bool MatchParameters(LuaState luaState, MethodBase method, MethodCache methodCache, int skipParam) + { + var paramInfo = method.GetParameters(); + int currentLuaParam = 1; + int nLuaParams = luaState.GetTop() - skipParam; + var paramList = new List(); + var outList = new List(); + var argTypes = new List(); + + foreach (var currentNetParam in paramInfo) + { + if (!currentNetParam.IsIn && currentNetParam.IsOut) // Skips out params + { + paramList.Add(null); + outList.Add(paramList.Count - 1); + continue; // Type does not match, ignore if the parameter is optional + } + + ExtractValue extractValue; + if (IsParamsArray(luaState, nLuaParams, currentLuaParam, currentNetParam, out extractValue)) + { + int count = (nLuaParams - currentLuaParam) + 1; + Type paramArrayType = currentNetParam.ParameterType.GetElementType(); + + Array paramArray = TableToArray(luaState, extractValue, paramArrayType, ref currentLuaParam, count); + paramList.Add(paramArray); + int index = paramList.LastIndexOf(paramArray); + var methodArg = new MethodArgs(); + methodArg.Index = index; + methodArg.ExtractValue = extractValue; + methodArg.IsParamsArray = true; + methodArg.ParameterType = paramArrayType; + argTypes.Add(methodArg); + continue; + } + + if (currentLuaParam > nLuaParams) + { + // Adds optional parameters + if (!currentNetParam.IsOptional) + return false; + paramList.Add(currentNetParam.DefaultValue); + continue; + } + + if (IsTypeCorrect(luaState, currentLuaParam, currentNetParam, out extractValue)) + { + // Type checking + var value = extractValue(luaState, currentLuaParam); + paramList.Add(value); + int index = paramList.Count - 1; + var methodArg = new MethodArgs(); + methodArg.Index = index; + methodArg.ExtractValue = extractValue; + methodArg.ParameterType = currentNetParam.ParameterType; + argTypes.Add(methodArg); + + if (currentNetParam.ParameterType.IsByRef) + outList.Add(index); + + currentLuaParam++; + continue; + } + + if (currentNetParam.IsOptional) + { + paramList.Add(currentNetParam.DefaultValue); + continue; + } + + return false; + } + + if (currentLuaParam != nLuaParams + 1) // Number of parameters does not match + return false; + + methodCache.args = paramList.ToArray(); + methodCache.cachedMethod = method; + methodCache.outList = outList.ToArray(); + methodCache.argTypes = argTypes.ToArray(); + + return true; + } + + /// + /// Returns true if the type is set and assigns the extract value + /// + /// + /// + /// + /// + /// + private bool IsTypeCorrect(LuaState luaState, int currentLuaParam, ParameterInfo currentNetParam, out ExtractValue extractValue) + { + extractValue = _translator.typeChecker.CheckLuaType(luaState, currentLuaParam, currentNetParam.ParameterType); + return extractValue != null; + } + + private bool IsParamsArray(LuaState luaState, int nLuaParams, int currentLuaParam, ParameterInfo currentNetParam, out ExtractValue extractValue) + { + extractValue = null; + + if (!currentNetParam.GetCustomAttributes(typeof(ParamArrayAttribute), false).Any()) + return false; + + bool isParamArray = nLuaParams < currentLuaParam; + + LuaType luaType = luaState.Type(currentLuaParam); + + if (luaType == LuaType.Table) + { + extractValue = _translator.typeChecker.GetExtractor(typeof(LuaTable)); + if (extractValue != null) + return true; + } + else + { + Type paramElementType = currentNetParam.ParameterType.GetElementType(); + + extractValue = _translator.typeChecker.CheckLuaType(luaState, currentLuaParam, paramElementType); + + if (extractValue != null) + return true; + } + return isParamArray; + } + } +} diff --git a/ExternalProjects/NLua/src/Method/EventHandlerContainer.cs b/ExternalProjects/NLua/src/Method/EventHandlerContainer.cs new file mode 100644 index 0000000000..f5ac6b8289 --- /dev/null +++ b/ExternalProjects/NLua/src/Method/EventHandlerContainer.cs @@ -0,0 +1,36 @@ +using System; +using System.Diagnostics; +using System.Collections.Generic; + +namespace NLua.Method +{ + /// + /// We keep track of what delegates we have auto attached to an event - to allow us to cleanly exit a NLua session + /// + internal class EventHandlerContainer : IDisposable + { + private readonly Dictionary _dict = new Dictionary(); + + public void Add(Delegate handler, RegisterEventHandler eventInfo) + { + _dict.Add(handler, eventInfo); + } + + public void Remove(Delegate handler) + { + bool found = _dict.Remove(handler); + Debug.Assert(found); + } + + /// + /// Remove any still registered handlers + /// + public void Dispose() + { + foreach (KeyValuePair pair in _dict) + pair.Value.RemovePending(pair.Key); + + _dict.Clear(); + } + } +} \ No newline at end of file diff --git a/ExternalProjects/NLua/src/Method/LuaClassHelper.cs b/ExternalProjects/NLua/src/Method/LuaClassHelper.cs new file mode 100644 index 0000000000..9c41458df4 --- /dev/null +++ b/ExternalProjects/NLua/src/Method/LuaClassHelper.cs @@ -0,0 +1,58 @@ +using System; + +namespace NLua.Method +{ + public class LuaClassHelper + { + /// + /// Gets the function called name from the provided table, + /// returning null if it does not exist + /// + public static LuaFunction GetTableFunction(LuaTable luaTable, string name) + { + if (luaTable == null) + return null; + + var funcObj = luaTable.RawGet(name) as LuaFunction; + + if (funcObj != null) + return funcObj; + return null; + } + + /// + /// Calls the provided function with the provided parameters + /// + public static object CallFunction(LuaFunction function, object[] args, Type[] returnTypes, object[] inArgs, int[] outArgs) + { + // args is the return array of arguments, inArgs is the actual array + // of arguments passed to the function (with in parameters only), outArgs + // has the positions of out parameters + object returnValue; + int iRefArgs; + object[] returnValues = function.Call(inArgs, returnTypes); + + if (returnValues == null || returnTypes.Length == 0) + return null; + + if (returnTypes[0] == typeof(void)) + { + returnValue = null; + iRefArgs = 0; + } + else + { + returnValue = returnValues[0]; + iRefArgs = 1; + } + + for (int i = 0; i < outArgs.Length; i++) + { + args[outArgs[i]] = returnValues[iRefArgs]; + iRefArgs++; + } + + return returnValue; + } + } +} diff --git a/ExternalProjects/NLua/src/Method/LuaDelegate.cs b/ExternalProjects/NLua/src/Method/LuaDelegate.cs new file mode 100644 index 0000000000..bc5548e9f4 --- /dev/null +++ b/ExternalProjects/NLua/src/Method/LuaDelegate.cs @@ -0,0 +1,47 @@ +using System; + +namespace NLua.Method +{ + public class LuaDelegate + { + public LuaFunction Function; + public Type[] ReturnTypes; + + public LuaDelegate() + { + Function = null; + ReturnTypes = null; + } + + public object CallFunction(object[] args, object[] inArgs, int[] outArgs) + { + // args is the return array of arguments, inArgs is the actual array + // of arguments passed to the function (with in parameters only), outArgs + // has the positions of out parameters + object returnValue; + int iRefArgs; + object[] returnValues = Function.Call(inArgs, ReturnTypes); + + if (ReturnTypes[0] == typeof(void)) + { + returnValue = null; + iRefArgs = 0; + } + else + { + returnValue = returnValues[0]; + iRefArgs = 1; + } + + // Sets the value of out and ref parameters (from + // the values returned by the Lua function). + for (int i = 0; i < outArgs.Length; i++) + { + args[outArgs[i]] = returnValues[iRefArgs]; + iRefArgs++; + } + + return returnValue; + } + } +} \ No newline at end of file diff --git a/ExternalProjects/NLua/src/Method/LuaEventHandler.cs b/ExternalProjects/NLua/src/Method/LuaEventHandler.cs new file mode 100644 index 0000000000..5ddb4b1eab --- /dev/null +++ b/ExternalProjects/NLua/src/Method/LuaEventHandler.cs @@ -0,0 +1,12 @@ +namespace NLua.Method +{ + public class LuaEventHandler + { + public LuaFunction Handler = null; + + public void HandleEvent(object[] args) + { + Handler.Call(args); + } + } +} \ No newline at end of file diff --git a/ExternalProjects/NLua/src/Method/LuaMethodWrapper.cs b/ExternalProjects/NLua/src/Method/LuaMethodWrapper.cs new file mode 100644 index 0000000000..724c2c21c9 --- /dev/null +++ b/ExternalProjects/NLua/src/Method/LuaMethodWrapper.cs @@ -0,0 +1,362 @@ +using System; +using System.Reflection; +using System.Collections.Generic; +using System.Linq; + +using NLua.Exceptions; +using NLua.Extensions; + +namespace NLua.Method +{ + /// + /// Argument extraction with type-conversion function + /// + internal delegate object ExtractValue(LuaState luaState, int stackPos); + + /// + /// Wrapper class for methods/constructors accessed from Lua. + /// + internal class LuaMethodWrapper + { + internal LuaNativeFunction InvokeFunction; + + internal readonly ObjectTranslator _translator; + internal readonly MethodBase _method; + + internal readonly ExtractValue _extractTarget; + internal readonly object _target; + internal readonly bool _isStatic; + + internal readonly string _methodName; + internal readonly MethodInfo[] _members; + + private MethodCache _lastCalledMethod; + + /// + /// Constructs the wrapper for a known MethodBase instance + /// + /// + /// + /// + /// + public LuaMethodWrapper(ObjectTranslator translator, object target, ProxyType targetType, MethodBase method) + { + InvokeFunction = Call; + _translator = translator; + _target = target; + _extractTarget = translator.typeChecker.GetExtractor(targetType); + _lastCalledMethod = new MethodCache(); + + _method = method; + _methodName = method.Name; + _isStatic = method.IsStatic; + + } + + /// + /// Constructs the wrapper for a known method name + /// + public LuaMethodWrapper(ObjectTranslator translator, ProxyType targetType, string methodName, BindingFlags bindingType) + { + InvokeFunction = Call; + + _translator = translator; + _methodName = methodName; + _extractTarget = translator.typeChecker.GetExtractor(targetType); + _lastCalledMethod = new MethodCache(); + + _isStatic = (bindingType & BindingFlags.Static) == BindingFlags.Static; + MethodInfo [] methods = GetMethodsRecursively(targetType.UnderlyingSystemType, + methodName, + bindingType | BindingFlags.Public); + _members = ReorderMethods(methods); + } + + private static MethodInfo[] ReorderMethods(MethodInfo[] m) + { + int len = m.Length; + + if (len < 2) + return m; + + return m. + GroupBy(c => c.GetParameters().Length). + SelectMany(g => g.OrderByDescending(ci => ci.ToString())). + ToArray(); + } + + internal MethodInfo[] GetMethodsRecursively(Type type, string methodName, BindingFlags bindingType) + { + if (type == typeof(object)) + return type.GetMethods(methodName, bindingType); + + var methods = type.GetMethods(methodName, bindingType); + var baseMethods = GetMethodsRecursively(type.BaseType, methodName, bindingType); + + return methods.Concat(baseMethods).ToArray(); + } + + /// + /// Convert C# exceptions into Lua errors + /// + /// num of things on stack + /// null for no pending exception + internal int SetPendingException(Exception e) + { + return _translator.interpreter.SetPendingException(e); + } + + internal void FillMethodArguments(LuaState luaState, int numStackToSkip) + { + object[] args = _lastCalledMethod.args; + + + for (int i = 0; i < _lastCalledMethod.argTypes.Length; i++) + { + MethodArgs type = _lastCalledMethod.argTypes[i]; + + int index = i + 1 + numStackToSkip; + + + if (_lastCalledMethod.argTypes[i].IsParamsArray) + { + int count = _lastCalledMethod.argTypes.Length - i; + Array paramArray = _translator.TableToArray(luaState, type.ExtractValue, type.ParameterType, index, count); + args[_lastCalledMethod.argTypes[i].Index] = paramArray; + } + else + { + args[type.Index] = type.ExtractValue(luaState, index); + } + + if (_lastCalledMethod.args[_lastCalledMethod.argTypes[i].Index] == null && + !luaState.IsNil(i + 1 + numStackToSkip)) + throw new LuaException(string.Format("Argument number {0} is invalid", (i + 1))); + } + } + + internal int PushReturnValue(LuaState luaState) + { + int nReturnValues = 0; + // Pushes out and ref return values + for (int index = 0; index < _lastCalledMethod.outList.Length; index++) + { + nReturnValues++; + _translator.Push(luaState, _lastCalledMethod.args[_lastCalledMethod.outList[index]]); + } + + // If not return void,we need add 1, + // or we will lost the function's return value + // when call dotnet function like "int foo(arg1,out arg2,out arg3)" in Lua code + if (!_lastCalledMethod.IsReturnVoid && nReturnValues > 0) + nReturnValues++; + + return nReturnValues < 1 ? 1 : nReturnValues; + } + + internal int CallInvoke(LuaState luaState, MethodBase method, object targetObject) + { + if (!luaState.CheckStack(_lastCalledMethod.outList.Length + 6)) + throw new LuaException("Lua stack overflow"); + + try + { + object result; + + if (method.IsConstructor) + result = ((ConstructorInfo)method).Invoke(_lastCalledMethod.args); + else + result = method.Invoke(targetObject, _lastCalledMethod.args); + + _translator.Push(luaState, result); + } + catch (TargetInvocationException e) + { + // Failure of method invocation + if (_translator.interpreter.UseTraceback) + e.GetBaseException().Data["Traceback"] = _translator.interpreter.GetDebugTraceback(); + return SetPendingException(e.GetBaseException()); + } + catch (Exception e) + { + return SetPendingException(e); + } + + return PushReturnValue(luaState); + } + + internal bool IsMethodCached(LuaState luaState, int numArgsPassed, int skipParams) + { + if (_lastCalledMethod.cachedMethod == null) + return false; + + if (numArgsPassed != _lastCalledMethod.argTypes.Length) + return false; + + // If there is no method overloads, is ok to use the cached method + if (_members == null || _members.Length == 1) + return true; + + return _translator.MatchParameters(luaState, _lastCalledMethod.cachedMethod, _lastCalledMethod, skipParams); + } + + internal int CallMethodFromName(LuaState luaState) + { + object targetObject = null; + + if (!_isStatic) + targetObject = _extractTarget(luaState, 1); + + int numStackToSkip = + _isStatic + ? 0 + : 1; // If this is an instance invoe we will have an extra arg on the stack for the targetObject + int numArgsPassed = luaState.GetTop() - numStackToSkip; + + // Cached? + if (IsMethodCached(luaState, numArgsPassed, numStackToSkip)) + { + MethodBase method = _lastCalledMethod.cachedMethod; + + if (!luaState.CheckStack(_lastCalledMethod.outList.Length + 6)) + throw new LuaException("Lua stack overflow"); + + FillMethodArguments(luaState, numStackToSkip); + + return CallInvoke(luaState, method, targetObject); + } + + // If we are running an instance variable, we can now pop the targetObject from the stack + if (!_isStatic) + { + if (targetObject == null) + { + _translator.ThrowError(luaState, + string.Format("instance method '{0}' requires a non null target object", _methodName)); + return 1; + } + + luaState.Remove(1); // Pops the receiver + } + + bool hasMatch = false; + string candidateName = null; + + foreach (var member in _members) + { + if (member.ReflectedType == null) + continue; + + candidateName = member.ReflectedType.Name + "." + member.Name; + bool isMethod = _translator.MatchParameters(luaState, member, _lastCalledMethod, 0); + + if (isMethod) + { + hasMatch = true; + break; + } + } + + if (!hasMatch) + { + string msg = (candidateName == null) + ? "Invalid arguments to method call" + : ("Invalid arguments to method: " + candidateName); + _translator.ThrowError(luaState, msg); + return 1; + } + + if (_lastCalledMethod.cachedMethod.ContainsGenericParameters) + return CallInvokeOnGenericMethod(luaState, (MethodInfo)_lastCalledMethod.cachedMethod, targetObject); + + return CallInvoke(luaState, _lastCalledMethod.cachedMethod, targetObject); + } + + internal int CallInvokeOnGenericMethod(LuaState luaState, MethodInfo methodToCall, object targetObject) + { + //need to make a concrete type of the generic method definition + var typeArgs = new List(); + + ParameterInfo [] parameters = methodToCall.GetParameters(); + + for (int i = 0; i < parameters.Length; i++) + { + ParameterInfo parameter = parameters[i]; + + if (!parameter.ParameterType.IsGenericParameter) + continue; + + typeArgs.Add(_lastCalledMethod.args[i].GetType()); + } + + MethodInfo concreteMethod = methodToCall.MakeGenericMethod(typeArgs.ToArray()); + + _translator.Push(luaState, concreteMethod.Invoke(targetObject, _lastCalledMethod.args)); + + return PushReturnValue(luaState); + } + + /// + /// Calls the method. Receives the arguments from the Lua stack + /// and returns values in it. + /// + internal int Call(IntPtr state) + { + var luaState = LuaState.FromIntPtr(state); + + MethodBase methodToCall = _method; + object targetObject = _target; + + if (!luaState.CheckStack(5)) + throw new LuaException("Lua stack overflow"); + + SetPendingException(null); + + // Method from name + if (methodToCall == null) + return CallMethodFromName(luaState); + + // Method from MethodBase instance + if (!methodToCall.ContainsGenericParameters) + { + if (!methodToCall.IsStatic && !methodToCall.IsConstructor && targetObject == null) + { + targetObject = _extractTarget(luaState, 1); + luaState.Remove(1); // Pops the receiver + } + + // Cached? + if (IsMethodCached(luaState, luaState.GetTop(), 0)) + { + if (!luaState.CheckStack(_lastCalledMethod.outList.Length + 6)) + throw new LuaException("Lua stack overflow"); + + FillMethodArguments(luaState, 0); + } + else if (!_translator.MatchParameters(luaState, methodToCall, _lastCalledMethod, 0)) + { + _translator.ThrowError(luaState, "Invalid arguments to method call"); + return 1; + } + } + else + { + if (!methodToCall.IsGenericMethodDefinition) + { + _translator.ThrowError(luaState, + "Unable to invoke method on generic class as the current method is an open generic method"); + return 1; + } + + _translator.MatchParameters(luaState, methodToCall, _lastCalledMethod, 0); + + return CallInvokeOnGenericMethod(luaState, (MethodInfo) methodToCall, targetObject); + } + + if (_isStatic) + targetObject = null; + + return CallInvoke(luaState, _lastCalledMethod.cachedMethod, targetObject); + } + } +} diff --git a/ExternalProjects/NLua/src/Method/MethodArgs.cs b/ExternalProjects/NLua/src/Method/MethodArgs.cs new file mode 100644 index 0000000000..2021e574ea --- /dev/null +++ b/ExternalProjects/NLua/src/Method/MethodArgs.cs @@ -0,0 +1,18 @@ +using System; + +namespace NLua.Method +{ + /// + /// Parameter information + /// + internal class MethodArgs + { + // Position of parameter + public int Index; + public Type ParameterType; + + // Type-conversion function + public ExtractValue ExtractValue; + public bool IsParamsArray; + } +} \ No newline at end of file diff --git a/ExternalProjects/NLua/src/Method/MethodCache.cs b/ExternalProjects/NLua/src/Method/MethodCache.cs new file mode 100644 index 0000000000..077a963fce --- /dev/null +++ b/ExternalProjects/NLua/src/Method/MethodCache.cs @@ -0,0 +1,41 @@ +using System; +using System.Reflection; + +namespace NLua.Method +{ + internal class MethodCache + { + public MethodCache() + { + args = Array.Empty(); + argTypes = Array.Empty(); + outList = Array.Empty(); + } + private MethodBase _cachedMethod; + + public MethodBase cachedMethod { + get + { + return _cachedMethod; + } + set + { + _cachedMethod = value; + var mi = value as MethodInfo; + + if (mi != null) + { + IsReturnVoid = mi.ReturnType == typeof(void); + } + } + } + + public bool IsReturnVoid; + // List or arguments + public object[] args; + // Positions of out parameters + public int[] outList; + // Types of parameters + public MethodArgs[] argTypes; + } +} diff --git a/ExternalProjects/NLua/src/Method/RegisterEventHandler.cs b/ExternalProjects/NLua/src/Method/RegisterEventHandler.cs new file mode 100644 index 0000000000..7c4b423c9f --- /dev/null +++ b/ExternalProjects/NLua/src/Method/RegisterEventHandler.cs @@ -0,0 +1,53 @@ +using System; +using System.Reflection; + +namespace NLua.Method +{ + internal class RegisterEventHandler + { + private readonly EventHandlerContainer _pendingEvents; + private readonly EventInfo _eventInfo; + private readonly object _target; + + public RegisterEventHandler(EventHandlerContainer pendingEvents, object target, EventInfo eventInfo) + { + _target = target; + _eventInfo = eventInfo; + _pendingEvents = pendingEvents; + } + + /// + /// Adds a new event handler + /// + public Delegate Add(LuaFunction function) + { + Delegate handlerDelegate = CodeGeneration.Instance.GetDelegate(_eventInfo.EventHandlerType, function); + return Add(handlerDelegate); + } + + public Delegate Add(Delegate handlerDelegate) + { + _eventInfo.AddEventHandler(_target, handlerDelegate); + _pendingEvents.Add(handlerDelegate, this); + + return handlerDelegate; + } + + /// + /// Removes an existing event handler + /// + public void Remove(Delegate handlerDelegate) + { + RemovePending(handlerDelegate); + _pendingEvents.Remove(handlerDelegate); + } + + /// + /// Removes an existing event handler (without updating the pending handlers list) + /// + internal void RemovePending(Delegate handlerDelegate) + { + _eventInfo.RemoveEventHandler(_target, handlerDelegate); + } + } +} \ No newline at end of file diff --git a/ExternalProjects/NLua/src/Native/DelegateExtensions.cs b/ExternalProjects/NLua/src/Native/DelegateExtensions.cs new file mode 100644 index 0000000000..d639b93fa6 --- /dev/null +++ b/ExternalProjects/NLua/src/Native/DelegateExtensions.cs @@ -0,0 +1,120 @@ +using System; +using System.Runtime.InteropServices; + +namespace NLua +{ + internal static class DelegateExtensions + { + public static LuaNativeFunction ToLuaFunction(this IntPtr ptr) + { + if (ptr == IntPtr.Zero) + return null; + + return Marshal.GetDelegateForFunctionPointer(ptr); + } + + public static IntPtr ToFunctionPointer(this LuaNativeFunction d) + { + if (d == null) + return IntPtr.Zero; + + return Marshal.GetFunctionPointerForDelegate(d); + } + + public static LuaHookFunction ToLuaHookFunction(this IntPtr ptr) + { + if (ptr == IntPtr.Zero) + return null; + + return Marshal.GetDelegateForFunctionPointer(ptr); + } + + public static IntPtr ToFunctionPointer(this LuaHookFunction d) + { + if (d == null) + return IntPtr.Zero; + + return Marshal.GetFunctionPointerForDelegate(d); + } + + public static LuaKFunction ToLuaKFunction(this IntPtr ptr) + { + if (ptr == IntPtr.Zero) + return null; + + return Marshal.GetDelegateForFunctionPointer(ptr); + } + + public static IntPtr ToFunctionPointer(this LuaKFunction d) + { + if (d == null) + return IntPtr.Zero; + + return Marshal.GetFunctionPointerForDelegate(d); + } + + public static LuaReader ToLuaReader(this IntPtr ptr) + { + if (ptr == IntPtr.Zero) + return null; + + return Marshal.GetDelegateForFunctionPointer(ptr); + } + + public static IntPtr ToFunctionPointer(this LuaReader d) + { + if (d == null) + return IntPtr.Zero; + + return Marshal.GetFunctionPointerForDelegate(d); + } + + public static LuaWriter ToLuaWriter(this IntPtr ptr) + { + if (ptr == IntPtr.Zero) + return null; + + return Marshal.GetDelegateForFunctionPointer(ptr); + } + + public static IntPtr ToFunctionPointer(this LuaWriter d) + { + if (d == null) + return IntPtr.Zero; + + return Marshal.GetFunctionPointerForDelegate(d); + } + + public static LuaAlloc ToLuaAlloc(this IntPtr ptr) + { + if (ptr == IntPtr.Zero) + return null; + + return Marshal.GetDelegateForFunctionPointer(ptr); + } + + public static IntPtr ToFunctionPointer(this LuaAlloc d) + { + if (d == null) + return IntPtr.Zero; + + return Marshal.GetFunctionPointerForDelegate(d); + } + + public static LuaWarnFunction ToLuaWarning(this IntPtr ptr) + { + if (ptr == IntPtr.Zero) + return null; + + return Marshal.GetDelegateForFunctionPointer(ptr); + } + + public static IntPtr ToFunctionPointer(this LuaWarnFunction d) + { + if (d == null) + return IntPtr.Zero; + + return Marshal.GetFunctionPointerForDelegate(d); + } + } +} diff --git a/ExternalProjects/NLua/src/Native/Delegates.cs b/ExternalProjects/NLua/src/Native/Delegates.cs new file mode 100644 index 0000000000..f5855a3092 --- /dev/null +++ b/ExternalProjects/NLua/src/Native/Delegates.cs @@ -0,0 +1,86 @@ +using System.Runtime.InteropServices; +using System.Security; +using charptr_t = System.IntPtr; +using lua_Debug = System.IntPtr; +using lua_KContext = System.IntPtr; +using lua_State = System.IntPtr; +using size_t = System.UIntPtr; +using voidptr_t = System.IntPtr; + +namespace NLua +{ + /// + /// Type for C# callbacks + /// In order to communicate properly with Lua, a C function must use the following protocol, which defines the way parameters and results are passed: a C function receives its arguments from Lua in its stack in direct order (the first argument is pushed first). So, when the function starts, lua_gettop(L) returns the number of arguments received by the function. The first argument (if any) is at index 1 and its last argument is at index lua_gettop(L). To return values to Lua, a C function just pushes them onto the stack, in direct order (the first result is pushed first), and returns the number of results. Any other value in the stack below the results will be properly discarded by Lua. Like a Lua function, a C function called by Lua can also return many results. + /// + /// + /// + [SuppressUnmanagedCodeSecurity] + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate int LuaNativeFunction(lua_State luaState); + + /// + /// Type for debugging hook functions callbacks. + /// + /// + /// + [SuppressUnmanagedCodeSecurity] + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void LuaHookFunction(lua_State luaState, lua_Debug ar); + + /// + /// Type for continuation functions + /// + /// + /// + /// + /// + [SuppressUnmanagedCodeSecurity] + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate int LuaKFunction(lua_State L, int status, lua_KContext ctx); + + /// + /// The reader function used by lua_load. Every time it needs another piece of the chunk, lua_load calls the reader, passing along its data parameter. The reader must return a pointer to a block of memory with a new piece of the chunk and set size to the block size + /// + /// + /// + /// + /// + [SuppressUnmanagedCodeSecurity] + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate charptr_t LuaReader(lua_State L, voidptr_t ud, ref size_t sz); + + /// + /// + /// + /// + /// + /// + /// + /// + [SuppressUnmanagedCodeSecurity] + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate int LuaWriter(lua_State L, voidptr_t p, size_t size, voidptr_t ud); + + /// + /// The type of the memory-allocation function used by Lua states. The allocator function must provide a functionality similar to realloc + /// + /// + /// + /// + /// + /// + [SuppressUnmanagedCodeSecurity] + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate voidptr_t LuaAlloc(voidptr_t ud, voidptr_t ptr, size_t osize, size_t nsize); + + /// + /// Type for warning functions + /// + /// + /// + /// + [SuppressUnmanagedCodeSecurity] + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void LuaWarnFunction(voidptr_t ud, charptr_t msg, int tocont); +} diff --git a/ExternalProjects/NLua/src/Native/LuaCompare.cs b/ExternalProjects/NLua/src/Native/LuaCompare.cs new file mode 100644 index 0000000000..93001f62a4 --- /dev/null +++ b/ExternalProjects/NLua/src/Native/LuaCompare.cs @@ -0,0 +1,21 @@ +namespace NLua +{ + /// + /// Used by Compare + /// + public enum LuaCompare + { + /// + /// compares for equality (==) + /// + Equal = 0, + /// + /// compares for less than + /// + LessThen = 1, + /// + /// compares for less or equal + /// + LessOrEqual = 2 + } +} diff --git a/ExternalProjects/NLua/src/Native/LuaDebug.cs b/ExternalProjects/NLua/src/Native/LuaDebug.cs new file mode 100644 index 0000000000..b0366cab3b --- /dev/null +++ b/ExternalProjects/NLua/src/Native/LuaDebug.cs @@ -0,0 +1,125 @@ +using System; +using System.Runtime.InteropServices; +using System.Text; + +namespace NLua +{ + /// + /// Structure for lua debug information + /// + /// + /// Do not change this struct because it must match the lua structure lua_Debug + /// + /// Reinhard Ostermeier + [StructLayout(LayoutKind.Sequential)] + public struct LuaDebug + { + /// + /// Get a LuaDebug from IntPtr + /// + /// + /// + public static LuaDebug FromIntPtr(IntPtr ar) + { + return Marshal.PtrToStructure(ar); + } + /// + /// Debug event code + /// + [MarshalAs(UnmanagedType.I4)] + public LuaHookEvent Event; + /// + /// a reasonable name for the given function. Because functions in Lua are first-class values, they do not have a fixed name: some functions can be the value of multiple global variables, while others can be stored only in a table field + /// + public string Name => Marshal.PtrToStringAnsi(name); + internal IntPtr name; + /// + /// explains the name field. The value of namewhat can be "global", "local", "method", "field", "upvalue", or "" (the empty string) + /// + public string NameWhat => Marshal.PtrToStringAnsi(what); + internal IntPtr nameWhat; + /// + /// the string "Lua" if the function is a Lua function, "C" if it is a C function, "main" if it is the main part of a chunk + /// + public string What => Marshal.PtrToStringAnsi(what); + internal IntPtr what; + /// + /// the name of the chunk that created the function. If source starts with a '@', it means that the function was defined in a file where the file name follows the '@'. + /// + /// + public string Source => Marshal.PtrToStringAnsi(source, SourceLength); + internal IntPtr source; + + /// + /// The length of the string source + /// + public int SourceLength => sourceLen.ToInt32(); + internal IntPtr sourceLen; + + /// + /// the current line where the given function is executing. When no line information is available, currentline is set to -1 + /// + public int CurrentLine; + /// + /// + /// + public int LineDefined; + /// + /// the line number where the definition of the function ends. + /// + public int LastLineDefined; + /// + /// number of upvalues + /// + public byte NumberUpValues; + /// + /// number of parameters + /// + public byte NumberParameters; + /// + /// true if the function is a vararg function (always true for C functions). + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsVarArg; /* (u) */ + /// + /// true if this function invocation was called by a tail call. In this case, the caller of this level is not in the stack. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsTailCall; /* (t) */ + + /// + /// The index on the stack of the first value being "transferred", that is, parameters in a call or return values in a return. (The other values are in consecutive indices.) Using this index, you can access and modify these values through lua_getlocal and lua_setlocal. This field is only meaningful during a call hook, denoting the first parameter, or a return hook, denoting the first value being returned. (For call hooks, this value is always 1.) + /// + public ushort IndexFirstValue; /* (r) index of first value transferred */ + + /// + /// The number of values being transferred (see previous item). (For calls of Lua functions, this value is always equal to nparams.) + /// + public ushort NumberTransferredValues; /* (r) number of transferred values */ + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 60)] + internal byte[] shortSource; + + /// + /// a "printable" version of source, to be used in error messages + /// + public string ShortSource + { + get + { + if (shortSource[0] == 0) + return string.Empty; + + int count = 0; + + while (count < shortSource.Length && shortSource[count] != 0) + { + count++; + } + + return Encoding.ASCII.GetString(shortSource, 0, count); + } + } + internal IntPtr i_ci; + } +} diff --git a/ExternalProjects/NLua/src/Native/LuaGC.cs b/ExternalProjects/NLua/src/Native/LuaGC.cs new file mode 100644 index 0000000000..7442151748 --- /dev/null +++ b/ExternalProjects/NLua/src/Native/LuaGC.cs @@ -0,0 +1,57 @@ +using System; + +namespace NLua +{ + /// + /// Garbage Collector operations + /// + public enum LuaGC + { + /// + /// Stops the garbage collector. + /// + Stop = 0, + /// + /// Restarts the garbage collector. + /// + Restart = 1, + /// + /// Performs a full garbage-collection cycle. + /// + Collect = 2, + /// + /// Returns the current amount of memory (in Kbytes) in use by Lua. + /// + Count = 3, + /// + /// Returns the remainder of dividing the current amount of bytes of memory in use by Lua by 1024 + /// + Countb = 4, + /// + /// Performs an incremental step of garbage collection. + /// + Step = 5, + /// + /// The options LUA_GCSETPAUSE and LUA_GCSETSTEPMUL of the function lua_gc are deprecated. You should use the new option LUA_GCINC to set them. + /// + [Obsolete("Deprecatad since Lua 5.4, Use Incremental instead")] + SetPause = 6, + /// + /// The options LUA_GCSETPAUSE and LUA_GCSETSTEPMUL of the function lua_gc are deprecated. You should use the new option LUA_GCINC to set them. + /// + [Obsolete("Deprecatad since Lua 5.4, Use Incremental instead")] + SetStepMultiplier = 7, + /// + /// returns a boolean that tells whether the collector is running + /// + IsRunning = 9, + /// + /// Changes the collector to generational mode with the given parameters (see §2.5.2). Returns the previous mode (LUA_GCGEN or LUA_GCINC). + /// + Generational = 10, + /// + /// Changes the collector to incremental mode with the given parameters (see §2.5.1). Returns the previous mode (LUA_GCGEN or LUA_GCINC). + /// + Incremental = 11, + } +} diff --git a/ExternalProjects/NLua/src/Native/LuaHookEvent.cs b/ExternalProjects/NLua/src/Native/LuaHookEvent.cs new file mode 100644 index 0000000000..bce9411aaa --- /dev/null +++ b/ExternalProjects/NLua/src/Native/LuaHookEvent.cs @@ -0,0 +1,29 @@ +namespace NLua +{ + /// + /// Whenever a hook is called, its ar argument has its field event set to the specific event that triggered the hook + /// + public enum LuaHookEvent + { + /// + /// The call hook: is called when the interpreter calls a function. The hook is called just after Lua enters the new function, before the function gets its arguments. + /// + Call = 0, + /// + /// The return hook: is called when the interpreter returns from a function. The hook is called just before Lua leaves the function. There is no standard way to access the values to be returned by the function. + /// + Return = 1, + /// + /// The line hook: is called when the interpreter is about to start the execution of a new line of code, or when it jumps back in the code (even to the same line). (This event only happens while Lua is executing a Lua function.) + /// + Line = 2, + /// + /// The count hook: is called after the interpreter executes every count instructions. (This event only happens while Lua is executing a Lua function.) + /// + Count = 3, + /// + /// Tail Call + /// + TailCall = 4, + } +} diff --git a/ExternalProjects/NLua/src/Native/LuaHookMask.cs b/ExternalProjects/NLua/src/Native/LuaHookMask.cs new file mode 100644 index 0000000000..d619d719b8 --- /dev/null +++ b/ExternalProjects/NLua/src/Native/LuaHookMask.cs @@ -0,0 +1,32 @@ +using System; + +namespace NLua +{ + /// + /// Lua Hook Event Masks + /// + [Flags] + public enum LuaHookMask + { + /// + /// Disabled hook + /// + Disabled = 0, + /// + /// The call hook: is called when the interpreter calls a function. The hook is called just after Lua enters the new function, before the function gets its arguments. + /// + Call = 1 << LuaHookEvent.Call, + /// + /// The return hook: is called when the interpreter returns from a function. The hook is called just before Lua leaves the function. There is no standard way to access the values to be returned by the function. + /// + Return = 1 << LuaHookEvent.Return, + /// + /// The line hook: is called when the interpreter is about to start the execution of a new line of code, or when it jumps back in the code (even to the same line). (This event only happens while Lua is executing a Lua function.) + /// + Line = 1 << LuaHookEvent.Line, + /// + /// The count hook: is called after the interpreter executes every count instructions. (This event only happens while Lua is executing a Lua function.) + /// + Count = 1 << LuaHookEvent.Count, + } +} diff --git a/ExternalProjects/NLua/src/Native/LuaOperation.cs b/ExternalProjects/NLua/src/Native/LuaOperation.cs new file mode 100644 index 0000000000..46db76959f --- /dev/null +++ b/ExternalProjects/NLua/src/Native/LuaOperation.cs @@ -0,0 +1,67 @@ +namespace NLua +{ + /// + /// Operation value used by Arith method + /// + public enum LuaOperation + { + /// + /// adition(+) + /// + Add = 0, + /// + /// substraction (-) + /// + Sub = 1, + /// + /// Multiplication (*) + /// + Mul = 2, + + /// + /// Modulo (%) + /// + Mod = 3, + + /// + /// Exponentiation (^) + /// + Pow = 4, + /// + /// performs float division (/) + /// + Div = 5, + /// + /// performs floor division (//) + /// + Idiv = 6, + /// + /// performs bitwise AND + /// + Band = 7, + /// + /// performs bitwise OR (|) + /// + Bor = 8, + /// + /// performs bitwise exclusive OR (~) + /// + Bxor = 9, + /// + /// performs left shift + /// + Shl = 10, + /// + /// performs right shift + /// + Shr = 11, + /// + /// performs mathematical negation (unary -) + /// + Unm = 12, + /// + /// performs bitwise NOT (~) + /// + Bnot = 13, + } +} diff --git a/ExternalProjects/NLua/src/Native/LuaRegister.cs b/ExternalProjects/NLua/src/Native/LuaRegister.cs new file mode 100644 index 0000000000..73c968a5e6 --- /dev/null +++ b/ExternalProjects/NLua/src/Native/LuaRegister.cs @@ -0,0 +1,21 @@ +using System.Runtime.InteropServices; + +namespace NLua +{ + /// + /// LuaRegister store the name and the delegate to register a native function + /// + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] + public struct LuaRegister + { + /// + /// Function name + /// + public string name; + /// + /// Function delegate + /// + [MarshalAs(UnmanagedType.FunctionPtr)] + public LuaNativeFunction function; + } +} diff --git a/ExternalProjects/NLua/src/Native/LuaRegistry.cs b/ExternalProjects/NLua/src/Native/LuaRegistry.cs new file mode 100644 index 0000000000..b80857a521 --- /dev/null +++ b/ExternalProjects/NLua/src/Native/LuaRegistry.cs @@ -0,0 +1,29 @@ +namespace NLua +{ + /// + /// Enum for pseudo-index used by registry table + /// + public enum LuaRegistry + { + /* LUAI_MAXSTACK 1000000 */ + /// + /// pseudo-index used by registry table + /// + Index = -1000000 - 1000 + } + + /// + /// Registry index + /// + public enum LuaRegistryIndex + { + /// + /// At this index the registry has the main thread of the state. + /// + MainThread = 1, + /// + /// At this index the registry has the global environment. + /// + Globals = 2, + } +} diff --git a/ExternalProjects/NLua/src/Native/LuaState.cs b/ExternalProjects/NLua/src/Native/LuaState.cs new file mode 100644 index 0000000000..eee7544365 --- /dev/null +++ b/ExternalProjects/NLua/src/Native/LuaState.cs @@ -0,0 +1,2210 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; + +using BizHawk.BizInvoke; +using BizHawk.Common; + +namespace NLua +{ + /// + /// Lua state class, main interface to use Lua library. + /// + public class LuaState : IDisposable + { + private static readonly NativeMethods NativeMethods; + + static LuaState() + { + var resolver = new DynamicLibraryImportResolver( + OSTailoredCode.IsUnixHost ? "liblua5.4.so" : "lua54.dll", hasLimitedLifetime: false); + NativeMethods = BizInvoker.GetInvoker(resolver, CallingConventionAdapters.Native); + } + + private IntPtr _luaState; + private readonly LuaState _mainState; + + /// + /// Internal Lua handle pointer. + /// + public IntPtr Handle => _luaState; + + /// + /// Encoding for the string conversions + /// UTF8 by default. + /// + public Encoding Encoding { get; set; } + + /// + /// Returns a pointer to a raw memory area associated with the given Lua state. The application can use this area for any purpose; Lua does not use it for anything. + /// Each new thread has this area initialized with a copy of the area of the main thread. + /// + /// + public IntPtr ExtraSpace => _luaState - IntPtr.Size; + + /// + /// Get the main thread object, if the object is the main thread will be equal this + /// + public LuaState MainThread => _mainState ?? this; + + /// + /// Initialize Lua state, and open the default libs + /// + /// flag to enable/disable opening the default libs + public LuaState(bool openLibs = true) + { + Encoding = Encoding.UTF8; + + _luaState = NativeMethods.luaL_newstate(); + + if (openLibs) + OpenLibs(); + + SetExtraObject(this, true); + } + + /// + /// Initialize Lua state with allocator function and user data value + /// This method will NOT open the default libs. + /// Creates a new thread running in a new, independent state. Returns NULL if it cannot create the thread or the state (due to lack of memory). The argument f is the allocator function; Lua does all memory allocation for this state through this function (see lua_Alloc). The second argument, ud, is an opaque pointer that Lua passes to the allocator in every call. + /// + /// LuaAlloc allocator function called to alloc/free memory + /// opaque pointer passed to allocator + public LuaState(LuaAlloc allocator, IntPtr ud) + { + Encoding = Encoding.UTF8; + + _luaState = NativeMethods.lua_newstate(allocator.ToFunctionPointer(), ud); + + SetExtraObject(this, true); + } + + private LuaState(IntPtr luaThread, LuaState mainState) + { + _mainState = mainState; + _luaState = luaThread; + Encoding = mainState.Encoding; + + SetExtraObject(this, false); + GC.SuppressFinalize(this); + } + + /// + /// Get the Lua object from IntPtr + /// Useful for LuaFunction callbacks, if the Lua object was already collected will return null. + /// + /// + /// + public static LuaState FromIntPtr(IntPtr luaState) + { + if (luaState == IntPtr.Zero) + return null; + + LuaState state = GetExtraObject(luaState); + if (state != null && state._luaState == luaState) + return state; + + return new LuaState(luaState, state.MainThread); + } + + /// + /// Finalizes an instance of the class. + /// + ~LuaState() + { + Dispose(false); + } + + /// + /// Dispose lua state + /// + /// + protected virtual void Dispose(bool disposing) + { + Close(); + } + + /// + /// Destroys all objects in the given Lua state (calling the corresponding garbage-collection metamethods, if any) and frees all dynamic memory used by this state + /// + public void Close() + { + if (_luaState == IntPtr.Zero || _mainState != null) + return; + + NativeMethods.lua_close(_luaState); + _luaState = IntPtr.Zero; + GC.SuppressFinalize(this); + } + + /// + /// Dispose the lua context (calling Close) + /// + public void Dispose() + { + Dispose(true); + } + + private void SetExtraObject(T obj, bool weak) where T : class + { + var handle = GCHandle.Alloc(obj, weak ? GCHandleType.Weak : GCHandleType.Normal); + IntPtr extraSpace = _luaState - IntPtr.Size; + Marshal.WriteIntPtr(extraSpace, GCHandle.ToIntPtr(handle)); + } + + private static T GetExtraObject(IntPtr luaState) where T : class + { + IntPtr extraSpace = luaState - IntPtr.Size; + IntPtr pointer = Marshal.ReadIntPtr(extraSpace); + var handle = GCHandle.FromIntPtr(pointer); + if (!handle.IsAllocated) + return null; + + return (T)handle.Target; + } + + + /// + /// Converts the acceptable index idx into an equivalent absolute index (that is, one that does not depend on the stack top). + /// + /// + /// + public int AbsIndex(int index) + { + return NativeMethods.lua_absindex(_luaState, index); + } + /// + /// Performs an arithmetic or bitwise operation over the two values (or one, in the case of negations) at the top of the stack, with the value at the top being the second operand, pops these values, and pushes the result of the operation. The function follows the semantics of the corresponding Lua operator (that is, it may call metamethods). + /// + /// + public void Arith(LuaOperation operation) + { + NativeMethods.lua_arith(_luaState, (int)operation); + } + + /// + /// Sets a new panic function and returns the old one + /// + /// + /// + public LuaNativeFunction AtPanic(LuaNativeFunction panicFunction) + { + IntPtr newPanicPtr = panicFunction.ToFunctionPointer(); + return NativeMethods.lua_atpanic(_luaState, newPanicPtr).ToLuaFunction(); + } + + /// + /// Calls a function. + /// To call a function you must use the following protocol: first, the function to be called is pushed onto the stack; then, the arguments to the function are pushed in direct order; + /// that is, the first argument is pushed first. Finally you call lua_call; nargs is the number of arguments that you pushed onto the stack. + /// All arguments and the function value are popped from the stack when the function is called. The function results are pushed onto the stack when the function returns. + /// The number of results is adjusted to nresults, unless nresults is LUA_MULTRET. In this case, all results from the function are pushed; + /// Lua takes care that the returned values fit into the stack space, but it does not ensure any extra space in the stack. The function results are pushed onto the stack in direct order (the first result is pushed first), so that after the call the last result is on the top of the stack. + /// + /// + /// + public void Call(int arguments, int results) + { + NativeMethods.lua_callk(_luaState, arguments, results, IntPtr.Zero, IntPtr.Zero); + } + + /// + /// This function behaves exactly like lua_call, but allows the called function to yield + /// + /// + /// + /// + /// + public void CallK(int arguments, int results, int context, LuaKFunction continuation) + { + IntPtr k = continuation.ToFunctionPointer(); + NativeMethods.lua_callk(_luaState, arguments, results, (IntPtr)context, k); + } + + /// + /// Ensures that the stack has space for at least n extra slots (that is, that you can safely push up to n values into it). It returns false if it cannot fulfill the request, + /// + /// + public bool CheckStack(int nExtraSlots) + { + return NativeMethods.lua_checkstack(_luaState, nExtraSlots) != 0; + } + + /// + /// Compares two Lua values. Returns 1 if the value at index index1 satisfies op when compared with the value at index index2 + /// + /// + /// + /// + /// + public bool Compare(int index1, int index2, LuaCompare comparison) + { + return NativeMethods.lua_compare(_luaState, index1, index2, (int)comparison) != 0; + } + + /// + /// Concatenates the n values at the top of the stack, pops them, and leaves the result at the top. If n is 1, the result is the single value on the stack (that is, the function does nothing); + /// + /// + public void Concat(int n) + { + NativeMethods.lua_concat(_luaState, n); + } + /// + /// Copies the element at index fromidx into the valid index toidx, replacing the value at that position + /// + /// + /// + public void Copy(int fromIndex, int toIndex) + { + NativeMethods.lua_copy(_luaState, fromIndex, toIndex); + } + + /// + /// Creates a new empty table and pushes it onto the stack. Parameter narr is a hint for how many elements the table will have as a sequence; parameter nrec is a hint for how many other elements the table will have + /// + /// + /// + public void CreateTable(int elements, int records) + { + NativeMethods.lua_createtable(_luaState, elements, records); + } + + /// + /// Dumps a function as a binary chunk. Receives a Lua function on the top of the stack and produces a binary chunk that, if loaded again, results in a function equivalent to the one dumped + /// + /// + /// + /// + /// + public int Dump(LuaWriter writer, IntPtr data, bool stripDebug) + { + return NativeMethods.lua_dump(_luaState, writer.ToFunctionPointer(), data, stripDebug ? 1 : 0); + } + + /// + /// Generates a Lua error, using the value at the top of the stack as the error object. This function does a long jump + /// (We want it to be inlined to avoid issues with managed stack) + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int Error() + { + return NativeMethods.lua_error(_luaState); + } + + /// + /// Controls the garbage collector. + /// + /// + /// + /// + public int GarbageCollector(LuaGC what, int data) + { + return NativeMethods.lua_gc(_luaState, (int)what, data); + } + + /// + /// Controls the garbage collector. + /// + /// + /// passed to lua_gc vargs + /// passed to lua_gc vargs + /// + public int GarbageCollector(LuaGC what, int data, int data2) + { + return NativeMethods.lua_gc_2(_luaState, (int)what, data, data2); + } + + /// + /// Returns the memory-allocation function of a given state. If ud is not NULL, Lua stores in *ud the opaque pointer given when the memory-allocator function was set. + /// + /// + /// + public LuaAlloc GetAllocFunction(ref IntPtr ud) + { + return NativeMethods.lua_getallocf(_luaState, ref ud).ToLuaAlloc(); + } + + /// + /// Pushes onto the stack the value t[k], where t is the value at the given index. As in Lua, this function may trigger a metamethod for the "index" event (see §2.4). + /// Returns the type of the pushed value. + /// + /// + /// + /// + public LuaType GetField(int index, string key) + { + return (LuaType)NativeMethods.lua_getfield(_luaState, index, key); + } + + /// + /// Pushes onto the stack the value t[k], where t is the value at the given index. As in Lua, this function may trigger a metamethod for the "index" event (see §2.4). + /// Returns the type of the pushed value. + /// + /// + /// + /// + public LuaType GetField(LuaRegistry index, string key) + { + return (LuaType)NativeMethods.lua_getfield(_luaState, (int)index, key); + } + + /// + /// Pushes onto the stack the value of the global name. Returns the type of that value + /// + /// + /// + public LuaType GetGlobal(string name) + { + return (LuaType)NativeMethods.lua_getglobal(_luaState, name); + } + + /// + /// Pushes onto the stack the value t[i], where t is the value at the given index + /// + /// + /// + /// Returns the type of the pushed value + public LuaType GetInteger(int index, long i) + { + return (LuaType)NativeMethods.lua_geti(_luaState, index, i); + } + + + /// + /// Gets information about a specific function or function invocation. + /// + /// + /// + /// This function returns false on error (for instance, an invalid option in what). + public bool GetInfo(string what, IntPtr ar) + { + return NativeMethods.lua_getinfo(_luaState, what, ar) != 0; + } + + /// + /// Gets information about a specific function or function invocation. + /// + /// + /// + /// This function returns false on error (for instance, an invalid option in what). + public bool GetInfo(string what, ref LuaDebug ar) + { + IntPtr pDebug = Marshal.AllocHGlobal(Marshal.SizeOf(ar)); + bool ret = false; + try + { + Marshal.StructureToPtr(ar, pDebug, false); + + ret = GetInfo(what, pDebug); + ar = LuaDebug.FromIntPtr(pDebug); + + } + finally + { + Marshal.FreeHGlobal(pDebug); + } + return ret; + } + + /// + /// Gets information about a local variable of a given activation record or a given function. + /// + /// + /// + /// + public string GetLocal(IntPtr ar, int n) + { + IntPtr ptr = NativeMethods.lua_getlocal(_luaState, ar, n); + return Marshal.PtrToStringAnsi(ptr); + } + + /// + /// Gets information about a local variable of a given activation record or a given function. + /// + /// + /// + /// + public string GetLocal(LuaDebug ar, int n) + { + IntPtr pDebug = Marshal.AllocHGlobal(Marshal.SizeOf(ar)); + string ret = string.Empty; + try + { + Marshal.StructureToPtr(ar, pDebug, false); + + ret = GetLocal(pDebug, n); + ar = LuaDebug.FromIntPtr(pDebug); + + } + finally + { + Marshal.FreeHGlobal(pDebug); + } + return ret; + } + + /// + /// If the value at the given index has a metatable, the function pushes that metatable onto the stack and returns 1 + /// + /// + /// + public bool GetMetaTable(int index) + { + return NativeMethods.lua_getmetatable(_luaState, index) != 0; + } + + /// + /// Gets information about the interpreter runtime stack. + /// + /// + /// + /// + public int GetStack(int level, IntPtr ar) + { + return NativeMethods.lua_getstack(_luaState, level, ar); + } + + /// + /// Gets information about the interpreter runtime stack. + /// + /// + /// + /// + public int GetStack(int level, ref LuaDebug ar) + { + IntPtr pDebug = Marshal.AllocHGlobal(Marshal.SizeOf(ar)); + int ret = 0; + try + { + Marshal.StructureToPtr(ar, pDebug, false); + + ret = GetStack(level, pDebug); + ar = LuaDebug.FromIntPtr(pDebug); + + } + finally + { + Marshal.FreeHGlobal(pDebug); + } + return ret; + } + + + /// + /// Pushes onto the stack the value t[k], where t is the value at the given index and k is the value at the top of the stack. + /// + /// + /// Returns the type of the pushed value + public LuaType GetTable(int index) + { + return (LuaType)NativeMethods.lua_gettable(_luaState, index); + } + + /// + /// Pushes onto the stack the value t[k], where t is the value at the given index and k is the value at the top of the stack. + /// + /// + /// Returns the type of the pushed value + public LuaType GetTable(LuaRegistry index) + { + return (LuaType)NativeMethods.lua_gettable(_luaState, (int)index); + } + + + /// + /// Returns the index of the top element in the stack. 0 means an empty stack. + /// + /// Returns the index of the top element in the stack. + public int GetTop() => NativeMethods.lua_gettop(_luaState); + + /// + /// Pushes onto the stack the n-th user value associated with the full userdata at the given index and returns the type of the pushed value. + /// If the userdata does not have that value, pushes nil and returns LUA_TNONE. + /// + /// + /// + /// Returns the type of the pushed value. + public int GetIndexedUserValue(int index, int nth) => NativeMethods.lua_getiuservalue(_luaState, index, nth); + + /// + /// Compatibility GetIndexedUserValue with constant 1 + /// + /// + /// + public int GetUserValue(int index) => GetIndexedUserValue(index, 1); + /// + /// Gets information about the n-th upvalue of the closure at index funcindex. It pushes the upvalue's value onto the stack and returns its name. Returns NULL (and pushes nothing) when the index n is greater than the number of upvalues. + /// For C functions, this function uses the empty string "" as a name for all upvalues. (For Lua functions, upvalues are the external local variables that the function uses, and that are consequently included in its closure.) + /// Upvalues have no particular order, as they are active through the whole function. They are numbered in an arbitrary order. + /// + /// + /// + /// Returns the type of the pushed value. + public string GetUpValue(int functionIndex, int n) + { + IntPtr ptr = NativeMethods.lua_getupvalue(_luaState, functionIndex, n); + return Marshal.PtrToStringAnsi(ptr); + } + + + /// + /// Returns the current hook function. + /// + public LuaHookFunction Hook => NativeMethods.lua_gethook(_luaState).ToLuaHookFunction(); + + /// + /// Returns the current hook count. + /// + public int HookCount => NativeMethods.lua_gethookcount(_luaState); + + /// + /// Returns the current hook mask. + /// + public LuaHookMask HookMask => (LuaHookMask)NativeMethods.lua_gethookmask(_luaState); + + /// + /// Moves the top element into the given valid index, shifting up the elements above this index to open space. This function cannot be called with a pseudo-index, because a pseudo-index is not an actual stack position. + /// + /// + public void Insert(int index) => NativeMethods.lua_rotate(_luaState, index, 1); + + /// + /// Returns if the value at the given index is a boolean + /// + /// + /// + public bool IsBoolean(int index) => Type(index) == LuaType.Boolean; + + /// + /// Returns if the value at the given index is a C(#) function + /// + /// + /// + public bool IsCFunction(int index) => NativeMethods.lua_iscfunction(_luaState, index) != 0; + + /// + /// Returns if the value at the given index is a function + /// + /// + /// + public bool IsFunction(int index) => Type(index) == LuaType.Function; + + /// + /// Returns if the value at the given index is an integer + /// + /// + /// + public bool IsInteger(int index) => NativeMethods.lua_isinteger(_luaState, index) != 0; + + /// + /// Returns if the value at the given index is light user data + /// + /// + /// + public bool IsLightUserData(int index) => Type(index) == LuaType.LightUserData; + + /// + /// Returns if the value at the given index is nil + /// + /// + /// + public bool IsNil(int index) => Type(index) == LuaType.Nil; + + /// + /// Returns if the value at the given index is none + /// + /// + /// + public bool IsNone(int index) => Type(index) == LuaType.None; + + /// + /// Check if the value at the index is none or nil + /// + /// + /// + public bool IsNoneOrNil(int index) => IsNone(index) || IsNil(index); + + /// + /// Returns if the value at the given index is a number + /// + /// + /// + public bool IsNumber(int index) => NativeMethods.lua_isnumber(_luaState, index) != 0; + + /// + /// Returns if the value at the given index is a string or a number (which is always convertible to a string) + /// + /// + /// + public bool IsStringOrNumber(int index) + { + return NativeMethods.lua_isstring(_luaState, index) != 0; + } + + /// + /// Returns if the value at the given index is a string + /// NOTE: This is different from the lua_isstring, which return true if the value is a number + /// + /// + /// + public bool IsString(int index) => Type(index) == LuaType.String; + + /// + /// Returns if the value at the given index is a table. + /// + /// + /// + public bool IsTable(int index) => Type(index) == LuaType.Table; + + /// + /// Returns if the value at the given index is a thread. + /// + /// + /// + public bool IsThread(int index) => Type(index) == LuaType.Thread; + + /// + /// Returns if the value at the given index is a user data. + /// + /// + /// + public bool IsUserData(int index) => NativeMethods.lua_isuserdata(_luaState, index) != 0; + + /// + /// Returns if the given coroutine can yield, and 0 otherwise + /// + public bool IsYieldable => NativeMethods.lua_isyieldable(_luaState) != 0; + + /// + /// Push the length of the value at the given index on the stack. It is equivalent to the '#' operator in Lua (see §3.4.7) and may trigger a metamethod for the "length" event (see §2.4). The result is pushed on the stack. + /// + /// + public void PushLength(int index) => NativeMethods.lua_len(_luaState, index); + + /// + /// Loads a Lua chunk without running it. If there are no errors, lua_load pushes the compiled chunk as a Lua function on top of the stack. Otherwise, it pushes an error message. + /// The lua_load function uses a user-supplied reader function to read the chunk (see lua_Reader). The data argument is an opaque value passed to the reader function. + /// + /// + /// + /// + /// + /// + public LuaStatus Load + (LuaReader reader, + IntPtr data, + string chunkName, + string mode) + { + return (LuaStatus)NativeMethods.lua_load(_luaState, + reader.ToFunctionPointer(), + data, + chunkName, + mode); + } + + /// + /// Creates a new empty table and pushes it onto the stack + /// + public void NewTable() => NativeMethods.lua_createtable(_luaState, 0, 0); + + /// + /// Creates a new thread, pushes it on the stack, and returns a pointer to a lua_State that represents this new thread. The new thread returned by this function shares with the original thread its global environment, but has an independent execution stack. + /// + /// + public LuaState NewThread() + { + IntPtr thread = NativeMethods.lua_newthread(_luaState); + return new LuaState(thread, this); + } + + /// + /// This function creates and pushes on the stack a new full userdata, with nuvalue associated Lua values, called user values, plus an associated block of raw memory with size bytes. (The user values can be set and read with the functions lua_setiuservalue and lua_getiuservalue.) + /// The function returns the address of the block of memory. + /// + /// + /// + /// + public IntPtr NewIndexedUserData(int size, int uv) + { + return NativeMethods.lua_newuserdatauv(_luaState, (UIntPtr)size, uv); + } + + /// + /// Compatibility NewIndexedUserData with constant parameter + /// + /// + /// + public IntPtr NewUserData(int size) + { + return NewIndexedUserData(size, 1); + } + + /// + /// Pops a key from the stack, and pushes a key–value pair from the table at the given index (the "next" pair after the given key). + /// + /// + /// + public bool Next(int index) => NativeMethods.lua_next(_luaState, index) != 0; + + /// + /// Calls a function in protected mode. + /// + /// + /// + /// + public LuaStatus PCall(int arguments, int results, int errorFunctionIndex) + { + return (LuaStatus)NativeMethods.lua_pcallk(_luaState, arguments, results, errorFunctionIndex, IntPtr.Zero, IntPtr.Zero); + } + + /// + /// This function behaves exactly like lua_pcall, but allows the called function to yield + /// + /// + /// + /// + /// + /// + public LuaStatus PCallK(int arguments, + int results, + int errorFunctionIndex, + int context, + LuaKFunction k) + { + return (LuaStatus)NativeMethods.lua_pcallk(_luaState, + arguments, + results, + errorFunctionIndex, + (IntPtr)context, + k.ToFunctionPointer()); + } + + /// + /// Pops n elements from the stack. + /// + /// + public void Pop(int n) => NativeMethods.lua_settop(_luaState, -n - 1); + + /// + /// Pushes a boolean value with value b onto the stack. + /// + /// + public void PushBoolean(bool b) => NativeMethods.lua_pushboolean(_luaState, b ? 1 : 0); + + /// + /// Pushes a new C closure onto the stack. When a C function is created, it is possible to associate + /// some values with it, thus creating a C closure (see §4.4); these values are then accessible to the function + /// whenever it is called. To associate values with a C function, first these values must be pushed onto the + /// stack (when there are multiple values, the first value is pushed first). + /// Then lua_pushcclosure is called to create and push the C function onto the stack, + /// with the argument n telling how many values will be associated with the function. + /// lua_pushcclosure also pops these values from the stack. + /// + /// + /// + public void PushCClosure(LuaNativeFunction function, int n) + { + NativeMethods.lua_pushcclosure(_luaState, function.ToFunctionPointer(), n); + } + + /// + /// Pushes a C function onto the stack. This function receives a pointer to a C function and pushes onto the stack a Lua value of type function that, when called, invokes the corresponding C function. + /// + /// + public void PushCFunction(LuaNativeFunction function) + { + PushCClosure(function, 0); + } + + /// + /// Pushes the global environment onto the stack. + /// + public void PushGlobalTable() + { + _ = NativeMethods.lua_rawgeti(_luaState, (int)LuaRegistry.Index, (int)LuaRegistryIndex.Globals); + } + /// + /// Pushes an integer with value n onto the stack. + /// + /// + public void PushInteger(long n) => NativeMethods.lua_pushinteger(_luaState, n); + + /// + /// Pushes a light userdata onto the stack. + /// Userdata represent C values in Lua. A light userdata represents a pointer, a void*. It is a value (like a number): you do not create it, it has no individual metatable, and it is not collected (as it was never created). A light userdata is equal to "any" light userdata with the same C address. + /// + /// + public void PushLightUserData(IntPtr data) + { + NativeMethods.lua_pushlightuserdata(_luaState, data); + } + + /// + /// Pushes a reference data (C# object) onto the stack. + /// This function uses lua_pushlightuserdata, but uses a GCHandle to store the reference inside the Lua side. + /// The CGHandle is create as Normal, and will be freed when the value is pop + /// + /// + /// + public void PushObject(T obj) + { + if (obj == null) + { + PushNil(); + return; + } + + var handle = GCHandle.Alloc(obj); + PushLightUserData(GCHandle.ToIntPtr(handle)); + } + + + /// + /// Pushes binary buffer onto the stack (usually UTF encoded string) or any arbitraty binary data + /// + /// + public void PushBuffer(byte[] buffer) + { + if (buffer == null) + { + PushNil(); + return; + } + + NativeMethods.lua_pushlstring(_luaState, buffer, (UIntPtr)buffer.Length); + } + + /// + /// Pushes a string onto the stack + /// + /// + public void PushString(string value) + { + if (value == null) + { + PushNil(); + return; + } + + byte[] buffer = Encoding.GetBytes(value); + PushBuffer(buffer); + } + + /// + /// Push a instring using string.Format + /// PushString("Foo {0}", 10); + /// + /// + /// + public void PushString(string value, params object[] args) + { + PushString(string.Format(value, args)); + } + + /// + /// Pushes a nil value onto the stack. + /// + public void PushNil() => NativeMethods.lua_pushnil(_luaState); + + /// + /// Pushes a double with value n onto the stack. + /// + /// + public void PushNumber(double number) => NativeMethods.lua_pushnumber(_luaState, number); + + + /// + /// Pushes the thread represented by L onto the stack. Returns true if this thread is the main thread of its state. + /// + /// + public bool PushThread() + { + return NativeMethods.lua_pushthread(_luaState) == 1; + } + + /// + /// Pushes a copy of the element at the given index onto the stack. (lua_pushvalue) + /// The method was renamed, since pushvalue is a bit vague + /// + /// + public void PushCopy(int index) + { + NativeMethods.lua_pushvalue(_luaState, index); + } + + /// + /// Returns true if the two values in indices index1 and index2 are primitively equal (that is, without calling the __eq metamethod). Otherwise returns false. Also returns false if any of the indices are not valid. + /// + /// + /// + /// + public bool RawEqual(int index1, int index2) + { + return NativeMethods.lua_rawequal(_luaState, index1, index2) != 0; + } + + /// + /// Similar to GetTable, but does a raw access (i.e., without metamethods). + /// + /// + /// Returns the type of the pushed value + public LuaType RawGet(int index) + { + return (LuaType)NativeMethods.lua_rawget(_luaState, index); + } + + /// + /// Similar to GetTable, but does a raw access (i.e., without metamethods). + /// + /// + /// Returns the type of the pushed value + public LuaType RawGet(LuaRegistry index) + { + return (LuaType)NativeMethods.lua_rawget(_luaState, (int)index); + } + + /// + /// Pushes onto the stack the value t[n], where t is the table at the given index. The access is raw, that is, it does not invoke the __index metamethod. + /// + /// + /// + /// Returns the type of the pushed value + public LuaType RawGetInteger(int index, long n) + { + return (LuaType)NativeMethods.lua_rawgeti(_luaState, index, n); + } + + /// + /// Pushes onto the stack the value t[n], where t is the table at the given index. The access is raw, that is, it does not invoke the __index metamethod. + /// + /// + /// + /// + public LuaType RawGetInteger(LuaRegistry index, long n) + { + return (LuaType)NativeMethods.lua_rawgeti(_luaState, (int)index, n); + } + + + /// + /// Pushes onto the stack the value t[k], where t is the table at the given index and k is the pointer p represented as a light userdata. The access is raw; that is, it does not invoke the __index metamethod. + /// + /// + /// + /// Returns the type of the pushed value. + public LuaType RawGetByHashCode(int index, object obj) + { + if (obj == null) + throw new ArgumentNullException(nameof(obj), "obj shouldn't be null"); + + return (LuaType)NativeMethods.lua_rawgetp(_luaState, index, (IntPtr)obj.GetHashCode()); + } + + /// + /// Returns the raw "length" of the value at the given index: for strings, this is the string length; for tables, this is the result of the length operator ('#') with no metamethods; for userdata, this is the size of the block of memory allocated for the userdata; for other values, it is 0. + /// + /// + /// + public int RawLen(int index) + { + return (int)NativeMethods.lua_rawlen(_luaState, index); + } + + /// + /// Similar to lua_settable, but does a raw assignment (i.e., without metamethods). + /// + /// + public void RawSet(int index) + { + NativeMethods.lua_rawset(_luaState, index); + } + + /// + /// Similar to lua_settable, but does a raw assignment (i.e., without metamethods). + /// + /// + public void RawSet(LuaRegistry index) + { + NativeMethods.lua_rawset(_luaState, (int)index); + } + + /// + /// Does the equivalent of t[i] = v, where t is the table at the given index and v is the value at the top of the stack. + /// This function pops the value from the stack. The assignment is raw, that is, it does not invoke the __newindex metamethod. + /// + /// index of table + /// value + public void RawSetInteger(int index, long i) + { + NativeMethods.lua_rawseti(_luaState, index, i); + } + + /// + /// Does the equivalent of t[i] = v, where t is the table at the given index and v is the value at the top of the stack. + /// This function pops the value from the stack. The assignment is raw, that is, it does not invoke the __newindex metamethod. + /// + /// + /// + public void RawSetInteger(LuaRegistry index, long i) + { + NativeMethods.lua_rawseti(_luaState, (int)index, i); + } + + + /// + /// Does the equivalent of t[p] = v, where t is the table at the given index, p is encoded as a light userdata, and v is the value at the top of the stack. + /// + /// + /// + public void RawSetByHashCode(int index, object obj) + { + if (obj == null) + throw new ArgumentNullException(nameof(obj), "obj shouldn't be null"); + + NativeMethods.lua_rawsetp(_luaState, index, (IntPtr)obj.GetHashCode()); + } + + /// + /// Sets the C# delegate f as the new value of global name + /// + /// + /// + public void Register(string name, LuaNativeFunction function) + { + PushCFunction(function); + SetGlobal(name); + } + + + /// + /// Removes the element at the given valid index, shifting down the elements above this index to fill the gap. This function cannot be called with a pseudo-index, because a pseudo-index is not an actual stack position. + /// + /// + public void Remove(int index) + { + Rotate(index, -1); + Pop(1); + } + + /// + /// Moves the top element into the given valid index without shifting any element (therefore replacing the value at that given index), and then pops the top element. + /// + /// + public void Replace(int index) + { + Copy(-1, index); + Pop(1); + } + + /// + /// Starts and resumes a coroutine in the given thread L. + /// To start a coroutine, you push onto the thread stack the main function plus any arguments; then you call lua_resume, with nargs being the number of arguments.This call returns when the coroutine suspends or finishes its execution. When it returns, * nresults is updated and the top of the stack contains the* nresults values passed to lua_yield or returned by the body function. lua_resume returns LUA_YIELD if the coroutine yields, LUA_OK if the coroutine finishes its execution without errors, or an error code in case of errors (see lua_pcall). In case of errors, the error object is on the top of the stack. + /// To resume a coroutine, you clear its stack, push only the values to be passed as results from yield, and then call lua_resume. + /// The parameter from represents the coroutine that is resuming L. If there is no such coroutine, this parameter can be NULL. + /// + /// + /// + /// + /// + public LuaStatus Resume(LuaState from, int arguments, out int results) + { + return (LuaStatus)NativeMethods.lua_resume(_luaState, from?._luaState ?? IntPtr.Zero, arguments, out results); + } + + /// + /// Compatibility Resume without results. + /// + /// + /// + /// + public LuaStatus Resume(LuaState from, int arguments) + { + int ignore; + return (LuaStatus)NativeMethods.lua_resume(_luaState, from?._luaState ?? IntPtr.Zero, arguments, out ignore); + } + + /// + /// Resets a thread, cleaning its call stack and closing all pending to-be-closed variables. Returns a status code: LUA_OK for no errors in closing methods, or an error status otherwise. In case of error, leaves the error object on the top of the stack, + /// + /// + public int ResetThread() + { + return NativeMethods.lua_resetthread(_luaState); + } + + /// + /// Rotates the stack elements between the valid index idx and the top of the stack. The elements are rotated n positions in the direction of the top, for a positive n, or -n positions in the direction of the bottom, for a negative n. The absolute value of n must not be greater than the size of the slice being rotated. This function cannot be called with a pseudo-index, because a pseudo-index is not an actual stack position. + /// + /// + /// + public void Rotate(int index, int n) + { + NativeMethods.lua_rotate(_luaState, index, n); + } + + /// + /// Changes the allocator function of a given state to f with user data ud. + /// + /// + /// + public void SetAllocFunction(LuaAlloc alloc, ref IntPtr ud) + { + NativeMethods.lua_setallocf(_luaState, alloc.ToFunctionPointer(), ud); + } + + /// + /// Does the equivalent to t[k] = v, where t is the value at the given index and v is the value at the top of the stack. + /// + /// + /// + public void SetField(int index, string key) + { + NativeMethods.lua_setfield(_luaState, index, key); + } + + /// + /// Sets the debugging hook function. + /// + /// Argument f is the hook function. mask specifies on which events the hook will be called: it is formed by a bitwise OR of the constants + /// + /// Hook function callback + /// hook mask + /// count (used only with LuaHookMas.Count) + public void SetHook(LuaHookFunction hookFunction, LuaHookMask mask, int count) + { + NativeMethods.lua_sethook(_luaState, hookFunction.ToFunctionPointer(), (int)mask, count); + } + + /// + /// Pops a value from the stack and sets it as the new value of global name. + /// + /// + public void SetGlobal(string name) + { + NativeMethods.lua_setglobal(_luaState, name); + } + + /// + /// Does the equivalent to t[n] = v, where t is the value at the given index and v is the value at the top of the stack. + /// + /// + /// + public void SetInteger(int index, long n) + { + NativeMethods.lua_seti(_luaState, index, n); + } + + /// + /// Sets the value of a local variable of a given activation record. It assigns the value at the top of the stack to the variable and returns its name. It also pops the value from the stack. + /// + /// + /// + /// Returns NULL (and pops nothing) when the index is greater than the number of active local variables. + public string SetLocal(IntPtr ar, int n) + { + IntPtr ptr = NativeMethods.lua_setlocal(_luaState, ar, n); + return Marshal.PtrToStringAnsi(ptr); + } + + /// + /// Sets the value of a local variable of a given activation record. It assigns the value at the top of the stack to the variable and returns its name. It also pops the value from the stack. + /// + /// + /// + /// Returns NULL (and pops nothing) when the index is greater than the number of active local variables. + public string SetLocal(LuaDebug ar, int n) + { + IntPtr pDebug = Marshal.AllocHGlobal(Marshal.SizeOf(ar)); + string ret = string.Empty; + try + { + Marshal.StructureToPtr(ar, pDebug, false); + + ret = SetLocal(pDebug, n); + ar = LuaDebug.FromIntPtr(pDebug); + + } + finally + { + Marshal.FreeHGlobal(pDebug); + } + return ret; + } + + /// + /// Pops a table from the stack and sets it as the new metatable for the value at the given index. + /// + /// + public void SetMetaTable(int index) + { + NativeMethods.lua_setmetatable(_luaState, index); + } + + /// + /// Does the equivalent to t[k] = v, where t is the value at the given index, v is the value at the top of the stack, and k is the value just below the top + /// + /// + public void SetTable(int index) + { + NativeMethods.lua_settable(_luaState, index); + } + + /// + /// Accepts any index, or 0, and sets the stack top to this index. If the new top is larger than the old one, then the new elements are filled with nil. If index is 0, then all stack elements are removed. + /// + /// + public void SetTop(int newTop) + { + NativeMethods.lua_settop(_luaState, newTop); + } + + /// + /// Sets the value of a closure's upvalue. It assigns the value at the top of the stack to the upvalue and returns its name. It also pops the value from the stack. + /// + /// + /// + /// Returns NULL (and pops nothing) when the index n is greater than the number of upvalues. + public string SetUpValue(int functionIndex, int n) + { + IntPtr ptr = NativeMethods.lua_setupvalue(_luaState, functionIndex, n); + return Marshal.PtrToStringAnsi(ptr); + } + + /// + /// Sets the warning function to be used by Lua to emit warnings (see lua_WarnFunction). The ud parameter sets the value ud passed to the warning function. + /// + /// + /// + public void SetWarningFunction(LuaWarnFunction function, IntPtr userData) + { + NativeMethods.lua_setwarnf(_luaState, function.ToFunctionPointer(), userData); + } + + /// + /// Pops a value from the stack and sets it as the new n-th user value associated to the full userdata at the given index. Returns 0 if the userdata does not have that value. + /// + /// + /// + public void SetIndexedUserValue(int index, int nth) + { + NativeMethods.lua_setiuservalue(_luaState, index, nth); + } + + /// + /// Compatibility SetIndexedUserValue with constant 1 + /// + /// + public void SetUserValue(int index) => SetIndexedUserValue(index, 1); + + /// + /// The status can be 0 (LUA_OK) for a normal thread, an error code if the thread finished the execution of a lua_resume with an error, or LUA_YIELD if the thread is suspended. + /// You can only call functions in threads with status LUA_OK. You can resume threads with status LUA_OK (to start a new coroutine) or LUA_YIELD (to resume a coroutine). + /// + public LuaStatus Status => (LuaStatus)NativeMethods.lua_status(_luaState); + + /// + /// Converts the zero-terminated string s to a number, pushes that number into the stack, + /// + /// + /// + public bool StringToNumber(string s) + { + return NativeMethods.lua_stringtonumber(_luaState, s) != UIntPtr.Zero; + } + + /// + /// Converts the Lua value at the given index to a C# boolean value + /// + /// + /// + public bool ToBoolean(int index) + { + return NativeMethods.lua_toboolean(_luaState, index) != 0; + } + + /// + /// Converts a value at the given index to a C# function. That value must be a C# function; otherwise, returns NULL + /// + /// + /// + public LuaNativeFunction ToCFunction(int index) + { + return NativeMethods.lua_tocfunction(_luaState, index).ToLuaFunction(); + } + + /// + /// Marks the given index in the stack as a to-be-closed "variable" (see §3.3.8). Like a to-be-closed variable in Lua, the value at that index in the stack will be closed when it goes out of scope. Here, in the context of a C function, to go out of scope means that the running function returns to Lua, there is an error, or the index is removed from the stack through lua_settop or lua_pop. An index marked as to-be-closed should not be removed from the stack by any other function in the API except lua_settop or lua_pop. + /// This function should not be called for an index that is equal to or below an already marked to-be-closed index. + /// This function can raise an out-of-memory error. In that case, the value in the given index is immediately closed, as if it was already marked. + /// + /// + public void ToClose(int index) + { + NativeMethods.lua_toclose(_luaState, index); + } + + /// + /// Converts the Lua value at the given index to the signed integral type lua_Integer. The Lua value must be an integer, or a number or string convertible to an integer (see §3.4.3); otherwise, lua_tointegerx returns 0. + /// + /// + /// + public long ToInteger(int index) + { + return NativeMethods.lua_tointegerx(_luaState, index, out _); + } + + /// + /// Converts the Lua value at the given index to the signed integral type lua_Integer. The Lua value must be an integer, or a number or string convertible to an integer (see §3.4.3); otherwise, lua_tointegerx returns 0. + /// + /// + /// + public long? ToIntegerX(int index) + { + long value = NativeMethods.lua_tointegerx(_luaState, index, out var isInteger); + if (isInteger != 0) + return value; + return null; + } + + /// + /// Converts the Lua value at the given as byte array + /// + /// + /// + public byte[] ToBuffer(int index) + { + return ToBuffer(index, true); + } + /// + /// Converts the Lua value at the given index to a byte array. + /// + /// + /// Calls __tostring field if present + /// + public byte[] ToBuffer(int index, bool callMetamethod) + { + UIntPtr len; + IntPtr buff; + + if (callMetamethod) + { + buff = NativeMethods.luaL_tolstring(_luaState, index, out len); + Pop(1); + } + else + { + buff = NativeMethods.lua_tolstring(_luaState, index, out len); + } + + if (buff == IntPtr.Zero) + return null; + + int length = (int)len; + if (length == 0) + return Array.Empty(); + + byte[] output = new byte[length]; + Marshal.Copy(buff, output, 0, length); + return output; + } + + /// + /// Converts the Lua value at the given index to a C# string + /// + /// + /// + public string ToString(int index) + { + return ToString(index, true); + } + /// + /// Converts the Lua value at the given index to a C# string + /// + /// + /// Calls __tostring field if present + /// + public string ToString(int index, bool callMetamethod) + { + byte[] buffer = ToBuffer(index, callMetamethod); + if (buffer == null) + return null; + return Encoding.GetString(buffer); + } + + /// + /// Converts the Lua value at the given index to a C# double + /// + /// + /// + public double ToNumber(int index) + { + return NativeMethods.lua_tonumberx(_luaState, index, out _); + } + + /// + /// Converts the Lua value at the given index to a C# double? + /// + /// + /// + public double? ToNumberX(int index) + { + double value = NativeMethods.lua_tonumberx(_luaState, index, out var isNumber); + if (isNumber != 0) + return value; + return null; + } + + /// + /// Converts the value at the given index to a generic C pointer (void*). The value can be a userdata, a table, a thread, or a function; otherwise, lua_topointer returns NULL. Different objects will give different pointers. There is no way to convert the pointer back to its original value. + /// Typically this function is used only for hashing and debug information. + /// + /// + /// + public IntPtr ToPointer(int index) + { + return NativeMethods.lua_topointer(_luaState, index); + } + + + /// + /// Converts the value at the given index to a Lua thread + /// or return the self if is the main thread + /// + /// + /// + public LuaState ToThread(int index) + { + IntPtr state = NativeMethods.lua_tothread(_luaState, index); + if (state == _luaState) + return this; + + return FromIntPtr(state); + } + + /// + /// Return an object (refence) at the index + /// Important if a object was push the object need to fetched using + /// this method, otherwise the C# object will never be collected + /// + /// + /// + /// True to free the GCHandle + /// + public T ToObject(int index, bool freeGCHandle = true) + { + if (IsNil(index) || !IsLightUserData(index)) + return default; + + IntPtr data = ToUserData(index); + if (data == IntPtr.Zero) + return default; + + var handle = GCHandle.FromIntPtr(data); + if (!handle.IsAllocated) + return default; + + var reference = (T)handle.Target; + + if (freeGCHandle) + handle.Free(); + + return reference; + } + + /// + /// If the value at the given index is a full userdata, returns its block address. If the value is a light userdata, returns its pointer. Otherwise, returns NULL + /// + /// + /// + public IntPtr ToUserData(int index) + { + return NativeMethods.lua_touserdata(_luaState, index); + } + + + /// + /// + /// + /// + public LuaType Type(int index) + { + return (LuaType)NativeMethods.lua_type(_luaState, index); + } + + /// + /// Returns the name of the type of the value at the given index. + /// + /// + /// Name of the type of the value at the given index + public string TypeName(LuaType type) + { + IntPtr ptr = NativeMethods.lua_typename(_luaState, (int)type); + return Marshal.PtrToStringAnsi(ptr); + } + + /// + /// Returns a unique identifier for the upvalue numbered n from the closure at index funcindex. + /// + /// + /// + /// + public long UpValueId(int functionIndex, int n) + { + return (long)NativeMethods.lua_upvalueid(_luaState, functionIndex, n); + } + + /// + /// Returns the pseudo-index that represents the i-th upvalue of the running function + /// + /// + /// + public static int UpValueIndex(int i) + { + return (int)LuaRegistry.Index - i; + } + + /// + /// Make the n1-th upvalue of the Lua closure at index funcindex1 refer to the n2-th upvalue of the Lua closure at index funcindex2 + /// + /// + /// + /// + /// + public void UpValueJoin(int functionIndex1, int n1, int functionIndex2, int n2) + { + NativeMethods.lua_upvaluejoin(_luaState, functionIndex1, n1, functionIndex2, n2); + } + + /// + /// Return the version of Lua (e.g 504) + /// + /// + public double Version() + { + return NativeMethods.lua_version(_luaState); + } + + /// + /// Exchange values between different threads of the same state. + /// This function pops n values from the current stack, and pushes them onto the stack to. + /// + /// + /// + public void XMove(LuaState to, int n) + { + if (to == null) + throw new ArgumentNullException(nameof(to), "to shouldn't be null"); + + NativeMethods.lua_xmove(_luaState, to._luaState, n); + } + + /// + /// This function is equivalent to lua_yieldk, but it has no continuation (see §4.7). Therefore, when the thread resumes, it continues the function that called the function calling lua_yield. + /// + /// + /// + public int Yield(int results) + { + return NativeMethods.lua_yieldk(_luaState, results, IntPtr.Zero, IntPtr.Zero); + } + + /// + /// Yields a coroutine (thread). When a C function calls lua_yieldk, the running coroutine suspends its execution, and the call to lua_resume that started this coroutine returns + /// + /// Number of values from the stack that will be passed as results to lua_resume. + /// + /// + /// + public int YieldK(int results, int context, LuaKFunction continuation) + { + IntPtr k = continuation.ToFunctionPointer(); + return NativeMethods.lua_yieldk(_luaState, results, (IntPtr)context, k); + } + + /* Auxialiary Library Functions */ + + /// + /// Checks whether cond is true. If it is not, raises an error with a standard message + /// + /// + /// + /// + public void ArgumentCheck(bool condition, int argument, string message) + { + if (condition) + return; + ArgumentError(argument, message); + } + + /// + /// Raises an error reporting a problem with argument arg of the C function that called it, using a standard message that includes extramsg as a comment: + /// + /// + /// + /// + public int ArgumentError(int argument, string message) + { + // TODO: Use C# exception for errors? + return NativeMethods.luaL_argerror(_luaState, argument, message); + } + + /// + /// If the object at index obj has a metatable and this metatable has a field e, this function calls this field passing the object as its only argument. + /// + /// + /// + /// If there is no metatable or no metamethod, this function returns false (without pushing any value on the stack) + public bool CallMetaMethod(int obj, string field) + { + return NativeMethods.luaL_callmeta(_luaState, obj, field) != 0; + } + + /// + /// Checks whether the function has an argument of any type (including nil) at position arg. + /// + /// + public void CheckAny(int argument) + { + NativeMethods.luaL_checkany(_luaState, argument); + } + + /// + /// Checks whether the function argument arg is an integer (or can be converted to an integer) + /// + /// + /// + public long CheckInteger(int argument) + { + return NativeMethods.luaL_checkinteger(_luaState, argument); + } + + /// + /// Checks whether the function argument arg is a string and returns this string; + /// + /// + /// + public byte[] CheckBuffer(int argument) + { + UIntPtr len; + IntPtr buff = NativeMethods.luaL_checklstring(_luaState, argument, out len); + if (buff == IntPtr.Zero) + return null; + + int length = (int)len; + if (length == 0) + return Array.Empty(); + + byte[] output = new byte[length]; + Marshal.Copy(buff, output, 0, length); + return output; + } + + /// + /// Checks whether the function argument arg is a string and returns this string; + /// + /// + /// + public string CheckString(int argument) + { + byte[] buffer = CheckBuffer(argument); + if (buffer == null) + return null; + return Encoding.GetString(buffer); + } + + /// + /// Checks whether the function argument arg is a number and returns this number. + /// + /// + /// + public double CheckNumber(int argument) + { + return NativeMethods.luaL_checknumber(_luaState, argument); + } + + + /// + /// Checks whether the function argument arg is a string and searches for this string in the array lst + /// + /// + /// + /// + /// + public int CheckOption(int argument, string def, string[] list) + { + return NativeMethods.luaL_checkoption(_luaState, argument, def, list); + } + + + /// + /// Grows the stack size to top + sz elements, raising an error if the stack cannot grow + /// + /// + /// + public void CheckStack(int newSize, string message) + { + NativeMethods.luaL_checkstack(_luaState, newSize, message); + } + + /// + /// Checks whether the function argument arg has type type + /// + /// + /// + public void CheckType(int argument, LuaType type) + { + NativeMethods.luaL_checktype(_luaState, argument, (int)type); + } + + /// + /// Checks whether the function argument arg is a userdata of the type tname + /// + /// + /// + /// + /// True to release the GCHandle + /// + public T CheckObject(int argument, string typeName, bool freeGCHandle = true) + { + if (IsNil(argument) || !IsLightUserData(argument)) + return default; + + IntPtr data = CheckUserData(argument, typeName); + if (data == IntPtr.Zero) + return default; + + var handle = GCHandle.FromIntPtr(data); + if (!handle.IsAllocated) + return default; + + var reference = (T)handle.Target; + + if (freeGCHandle) + handle.Free(); + + return reference; + } + + /// + /// Checks whether the function argument arg is a userdata of the type tname (see luaL_newmetatable) and returns the userdata address + /// + /// + /// + /// + public IntPtr CheckUserData(int argument, string typeName) + { + return NativeMethods.luaL_checkudata(_luaState, argument, typeName); + } + + /// + /// Loads and runs the given file + /// + /// + /// It returns false if there are no errors or true in case of errors. + public bool DoFile(string file) + { + bool hasError = LoadFile(file) != LuaStatus.OK || PCall(0, -1, 0) != LuaStatus.OK; + return hasError; + } + + /// + /// Loads and runs the given string + /// + /// + /// It returns false if there are no errors or true in case of errors. + public bool DoString(string file) + { + bool hasError = LoadString(file) != LuaStatus.OK || PCall(0, -1, 0) != LuaStatus.OK; + return hasError; + } + + /// + /// Raises an error. The error message format is given by fmt plus any extra arguments + /// + /// + /// + /// + public int Error(string value, params object[] v) + { + string message = string.Format(value, v); + return NativeMethods.luaL_error(_luaState, message); + } + + /// + /// This function produces the return values for process-related functions in the standard library + /// + /// + /// + public int ExecResult(int stat) + { + return NativeMethods.luaL_execresult(_luaState, stat); + } + + /// + /// This function produces the return values for file-related functions in the standard library + /// + /// + /// + /// + public int FileResult(int stat, string fileName) + { + return NativeMethods.luaL_fileresult(_luaState, stat, fileName); + } + + /// + /// Pushes onto the stack the field e from the metatable of the object at index obj and returns the type of the pushed value + /// + /// + /// + /// + public LuaType GetMetaField(int obj, string field) + { + return (LuaType)NativeMethods.luaL_getmetafield(_luaState, obj, field); + } + + /// + /// Pushes onto the stack the metatable associated with name tname in the registry (see luaL_newmetatable) (nil if there is no metatable associated with that name) + /// + /// + /// Returns the type of the pushed value. + public LuaType GetMetaTable(string tableName) + { + return GetField(LuaRegistry.Index, tableName); + } + + /// + /// Ensures that the value t[fname], where t is the value at index idx, is a table, and pushes that table onto the stack. Returns true if it finds a previous table there and false if it creates a new table + /// + /// + /// + /// + public bool GetSubTable(int index, string name) + { + return NativeMethods.luaL_getsubtable(_luaState, index, name) != 0; + } + + /// + /// Returns the "length" of the value at the given index as a number; it is equivalent to the '#' operator in Lua + /// + /// + /// + public long Length(int index) => NativeMethods.luaL_len(_luaState, index); + + /// + /// Loads a buffer as a Lua chunk + /// + /// + /// + /// + /// + public LuaStatus LoadBuffer(byte[] buffer, string name, string mode) + { + if (buffer == null) + throw new ArgumentNullException(nameof(buffer), "buffer shouldn't be null"); + + return (LuaStatus)NativeMethods.luaL_loadbufferx(_luaState, buffer, (UIntPtr)buffer.Length, name, mode); + } + + /// + /// + /// + /// + /// + public LuaStatus LoadBuffer(byte[] buffer, string name) + { + return LoadBuffer(buffer, name, null); + } + + /// + /// Loads a buffer as a Lua chunk + /// + /// + /// + public LuaStatus LoadBuffer(byte[] buffer) + { + return LoadBuffer(buffer, null, null); + + } + + /// + /// Loads a string as a Lua chunk + /// + /// + /// + /// + public LuaStatus LoadString(string chunk, string name) + { + byte[] buffer = Encoding.GetBytes(chunk); + return LoadBuffer(buffer, name); + } + + /// + /// Loads a string as a Lua chunk + /// + /// + /// + public LuaStatus LoadString(string chunk) + { + return LoadString(chunk, null); + } + + /// + /// Loads a file as a Lua chunk. This function uses lua_load to load the chunk in the file named filename + /// + /// + /// + /// The status of operation + public LuaStatus LoadFile(string file, string mode) + { + return (LuaStatus)NativeMethods.luaL_loadfilex(_luaState, file, mode); + } + + /// + /// Loads a file as a Lua chunk. + /// + /// + /// Return the status + public LuaStatus LoadFile(string file) + { + return LoadFile(file, null); + } + + /// + /// Creates a new table and registers there the functions in list library. + /// + /// + public void NewLib(LuaRegister[] library) + { + NewLibTable(library); + SetFuncs(library, 0); + } + + /// + /// Creates a new table with a size optimized to store all entries in the array l (but does not actually store them) + /// + /// + public void NewLibTable(LuaRegister[] library) + { + if (library == null) + throw new ArgumentNullException(nameof(library), "library shouldn't be null"); + + CreateTable(0, library.Length); + } + + /// + /// Creates a new table to be used as a metatable for userdata + /// + /// + /// If the registry already has the key tname, returns false., + public bool NewMetaTable(string name) + { + return NativeMethods.luaL_newmetatable(_luaState, name) != 0; + } + + /// + /// Opens all standard Lua libraries into the given state. + /// + public void OpenLibs() + { + NativeMethods.luaL_openlibs(_luaState); + } + + /// + /// If the function argument arg is an integer (or convertible to an integer), returns this integer. If this argument is absent or is nil, returns d + /// + /// + /// default value + /// + public long OptInteger(int argument, long d) + { + return NativeMethods.luaL_optinteger(_luaState, argument, d); + } + + /// + /// If the function argument arg is a string, returns this string. If this argument is absent or is nil, returns d /// + /// + /// + /// + public byte[] OptBuffer(int index, byte[] def) + { + if (IsNoneOrNil(index)) + return def; + + return CheckBuffer(index); + } + + /// + /// If the function argument arg is a string, returns this string. If this argument is absent or is nil, returns d + /// + /// + /// + /// + public string OptString(int index, string def) + { + if (IsNoneOrNil(index)) + return def; + + return CheckString(index); + } + + + /// + /// If the function argument arg is a number, returns this number. If this argument is absent or is nil, returns d + /// + /// + /// + /// + public double OptNumber(int index, double def) + { + return NativeMethods.luaL_optnumber(_luaState, index, def); + } + + /// + /// Creates and returns a reference, in the table at index t, for the object at the top of the stack (and pops the object). + /// + /// + /// + public int Ref(LuaRegistry tableIndex) + { + return NativeMethods.luaL_ref(_luaState, (int)tableIndex); + } + + /// + /// If modname is not already present in package.loaded, calls function openf with string modname as an argument and sets the call result in package.loaded[modname], as if that function has been called through require + /// + /// + /// + /// + public void RequireF(string moduleName, LuaNativeFunction openFunction, bool global) + { + NativeMethods.luaL_requiref(_luaState, moduleName, openFunction.ToFunctionPointer(), global ? 1 : 0); + } + + /// + /// Registers all functions in the array l (see luaL_Reg) into the table on the top of the stack (below optional upvalues, see next). /// + /// + /// + public void SetFuncs(LuaRegister[] library, int numberUpValues) + { + NativeMethods.luaL_setfuncs(_luaState, library, numberUpValues); + } + + /// + /// Sets the metatable of the object at the top of the stack as the metatable associated with name tname in the registry + /// + /// + public void SetMetaTable(string name) + { + NativeMethods.luaL_setmetatable(_luaState, name); + } + + /// + /// Test if the value at index is a reference data + /// + /// + /// + /// + /// True to release the GCHandle of object + /// + public T TestObject(int argument, string typeName, bool freeGCHandle = true) + { + if (IsNil(argument) || !IsLightUserData(argument)) + return default; + + IntPtr data = TestUserData(argument, typeName); + if (data == IntPtr.Zero) + return default; + + var handle = GCHandle.FromIntPtr(data); + if (!handle.IsAllocated) + return default; + + var reference = (T)handle.Target; + if (freeGCHandle) + handle.Free(); + + return reference; + } + + /// + /// This function works like luaL_checkudata, except that, when the test fails, it returns NULL instead of raising an error. + /// + /// + /// + /// + public IntPtr TestUserData(int argument, string typeName) + { + return NativeMethods.luaL_testudata(_luaState, argument, typeName); + } + + /// + /// Creates and pushes a traceback of the stack L1 + /// + /// + /// Tells at which level to start the traceback + public void Traceback(LuaState state, int level = 0) + { + Traceback(state, null, level); + } + + /// + /// Creates and pushes a traceback of the stack L1 + /// + /// + /// appended at the beginning of the traceback + /// Tells at which level to start the traceback + public void Traceback(LuaState state, string message, int level) + { + if (state == null) + throw new ArgumentNullException(nameof(state), "state shouldn't be null"); + + NativeMethods.luaL_traceback(_luaState, state._luaState, message, level); + } + + /// + /// Returns the name of the type of the value at the given index. + /// + /// + /// + public string TypeName(int index) + { + LuaType type = Type(index); + return TypeName(type); + } + + /// + /// Releases reference ref from the table at index t (see luaL_ref). The entry is removed from the table, so that the referred object can be collected. The reference ref is also freed to be used again + /// + /// + /// + public void Unref(LuaRegistry tableIndex, int reference) + { + NativeMethods.luaL_unref(_luaState, (int)tableIndex, reference); + } + + /// + /// Emits a warning with the given message. A message in a call with tocont true should be continued in another call to this function. + /// + /// + /// + public void Warning(string message, bool toContinue) + { + NativeMethods.lua_warning(_luaState, message, toContinue ? 1 : 0); + } + + + /// + /// Pushes onto the stack a string identifying the current position of the control at level lvl in the call stack + /// + /// + public void Where(int level) + { + NativeMethods.luaL_where(_luaState, level); + } + } +} + diff --git a/ExternalProjects/NLua/src/Native/LuaStatus.cs b/ExternalProjects/NLua/src/Native/LuaStatus.cs new file mode 100644 index 0000000000..8892a522fb --- /dev/null +++ b/ExternalProjects/NLua/src/Native/LuaStatus.cs @@ -0,0 +1,33 @@ +namespace NLua +{ + /// + /// Lua Load/Call status return + /// + public enum LuaStatus + { + /// + /// success + /// + OK = 0, + /// + /// Yield + /// + Yield = 1, + /// + /// a runtime error. + /// + ErrRun = 2, + /// + /// syntax error during precompilation + /// + ErrSyntax = 3, + /// + /// memory allocation error. For such errors, Lua does not call the message handler. + /// + ErrMem = 4, + /// + /// error while running the message handler. + /// + ErrErr = 5, + } +} diff --git a/ExternalProjects/NLua/src/Native/LuaType.cs b/ExternalProjects/NLua/src/Native/LuaType.cs new file mode 100644 index 0000000000..64be0920c0 --- /dev/null +++ b/ExternalProjects/NLua/src/Native/LuaType.cs @@ -0,0 +1,50 @@ +namespace NLua +{ + /// + /// Lua types + /// + public enum LuaType + { + /// + /// + /// + None = -1, + /// + /// LUA_TNIL + /// + Nil = 0, + /// + /// LUA_TBOOLEAN + /// + Boolean = 1, + /// + /// LUA_TLIGHTUSERDATA + /// + LightUserData = 2, + /// + /// LUA_TNUMBER + /// + Number = 3, + /// + /// LUA_TSTRING + /// + String = 4, + /// + /// LUA_TTABLE + /// + Table = 5, + /// + /// LUA_TFUNCTION + /// + Function = 6, + /// + /// LUA_TUSERDATA + /// + UserData = 7, + /// + /// LUA_TTHREAD + /// + /// // + Thread = 8, + } +} diff --git a/ExternalProjects/NLua/src/Native/NativeMethods.cs b/ExternalProjects/NLua/src/Native/NativeMethods.cs new file mode 100644 index 0000000000..d144f16703 --- /dev/null +++ b/ExternalProjects/NLua/src/Native/NativeMethods.cs @@ -0,0 +1,439 @@ +// ReSharper disable IdentifierTypo +using System.Runtime.InteropServices; +using System.Security; + +using BizHawk.BizInvoke; + +using charptr_t = System.IntPtr; +using lua_Alloc = System.IntPtr; +using lua_CFunction = System.IntPtr; +using lua_Debug = System.IntPtr; +using lua_Hook = System.IntPtr; +using lua_Integer = System.Int64; +using lua_KContext = System.IntPtr; +using lua_KFunction = System.IntPtr; +using lua_Number = System.Double; +using lua_Number_ptr = System.IntPtr; +using lua_Reader = System.IntPtr; +using lua_State = System.IntPtr; +using lua_WarnFunction = System.IntPtr; +using lua_Writer = System.IntPtr; +using size_t = System.UIntPtr; +using voidptr_t = System.IntPtr; + +namespace NLua +{ + [SuppressUnmanagedCodeSecurity] + public abstract class NativeMethods + { + private const CallingConvention cc = CallingConvention.Cdecl; + +#pragma warning disable SA1121 // Use built-in type alias + + [BizImport(cc)] + public abstract int lua_absindex(lua_State luaState, int idx); + + [BizImport(cc)] + public abstract void lua_arith(lua_State luaState, int op); + + [BizImport(cc)] + public abstract lua_CFunction lua_atpanic(lua_State luaState, lua_CFunction panicf); + + [BizImport(cc)] + public abstract void lua_callk(lua_State luaState, int nargs, int nresults, lua_KContext ctx, lua_KFunction k); + + [BizImport(cc)] + public abstract int lua_checkstack(lua_State luaState, int extra); + + [BizImport(cc)] + public abstract void lua_close(lua_State luaState); + + [BizImport(cc)] + public abstract int lua_compare(lua_State luaState, int index1, int index2, int op); + + [BizImport(cc)] + public abstract void lua_concat(lua_State luaState, int n); + + [BizImport(cc)] + public abstract void lua_copy(lua_State luaState, int fromIndex, int toIndex); + + [BizImport(cc)] + public abstract void lua_createtable(lua_State luaState, int elements, int records); + + [BizImport(cc)] + public abstract int lua_dump(lua_State luaState, lua_Writer writer, voidptr_t data, int strip); + + [BizImport(cc)] + public abstract int lua_error(lua_State luaState); + + [BizImport(cc)] + public abstract int lua_gc(lua_State luaState, int what, int data); + + [BizImport(cc, EntryPoint = "lua_gc")] + public abstract int lua_gc_2(lua_State luaState, int what, int data, int data2); + + [BizImport(cc)] + public abstract lua_Alloc lua_getallocf(lua_State luaState, ref voidptr_t ud); + + [BizImport(cc)] + public abstract int lua_getfield(lua_State luaState, int index, string k); + + [BizImport(cc)] + public abstract int lua_getglobal(lua_State luaState, string name); + + [BizImport(cc)] + public abstract lua_Hook lua_gethook(lua_State luaState); + + [BizImport(cc)] + public abstract int lua_gethookcount(lua_State luaState); + + [BizImport(cc)] + public abstract int lua_gethookmask(lua_State luaState); + + [BizImport(cc)] + public abstract int lua_geti(lua_State luaState, int index, long i); + + [BizImport(cc)] + public abstract int lua_getinfo(lua_State luaState, string what, lua_Debug ar); + + [BizImport(cc)] + public abstract int lua_getiuservalue(lua_State luaState, int idx, int n); + + [BizImport(cc)] + public abstract charptr_t lua_getlocal(lua_State luaState, lua_Debug ar, int n); + + [BizImport(cc)] + public abstract int lua_getmetatable(lua_State luaState, int index); + + [BizImport(cc)] + public abstract int lua_getstack(lua_State luaState, int level, lua_Debug n); + + [BizImport(cc)] + public abstract int lua_gettable(lua_State luaState, int index); + + [BizImport(cc)] + public abstract int lua_gettop(lua_State luaState); + + [BizImport(cc)] + public abstract charptr_t lua_getupvalue(lua_State luaState, int funcIndex, int n); + + [BizImport(cc)] + public abstract int lua_iscfunction(lua_State luaState, int index); + + [BizImport(cc)] + public abstract int lua_isinteger(lua_State luaState, int index); + + [BizImport(cc)] + public abstract int lua_isnumber(lua_State luaState, int index); + + [BizImport(cc)] + public abstract int lua_isstring(lua_State luaState, int index); + + [BizImport(cc)] + public abstract int lua_isuserdata(lua_State luaState, int index); + + [BizImport(cc)] + public abstract int lua_isyieldable(lua_State luaState); + + [BizImport(cc)] + public abstract void lua_len(lua_State luaState, int index); + + [BizImport(cc)] + public abstract int lua_load + (lua_State luaState, + lua_Reader reader, + voidptr_t data, + string chunkName, + string mode); + + [BizImport(cc)] + public abstract lua_State lua_newstate(lua_Alloc allocFunction, voidptr_t ud); + + [BizImport(cc)] + public abstract lua_State lua_newthread(lua_State luaState); + + [BizImport(cc)] + public abstract voidptr_t lua_newuserdatauv(lua_State luaState, size_t size, int nuvalue); + + [BizImport(cc)] + public abstract int lua_next(lua_State luaState, int index); + + [BizImport(cc)] + public abstract int lua_pcallk + (lua_State luaState, + int nargs, + int nresults, + int errorfunc, + lua_KContext ctx, + lua_KFunction k); + + [BizImport(cc)] + public abstract void lua_pushboolean(lua_State luaState, int value); + + [BizImport(cc)] + public abstract void lua_pushcclosure(lua_State luaState, lua_CFunction f, int n); + + [BizImport(cc)] + public abstract void lua_pushinteger(lua_State luaState, lua_Integer n); + + [BizImport(cc)] + public abstract void lua_pushlightuserdata(lua_State luaState, voidptr_t udata); + + [BizImport(cc)] + public abstract charptr_t lua_pushlstring(lua_State luaState, byte[] s, size_t len); + + [BizImport(cc)] + public abstract void lua_pushnil(lua_State luaState); + + [BizImport(cc)] + public abstract void lua_pushnumber(lua_State luaState, lua_Number number); + + [BizImport(cc)] + public abstract int lua_pushthread(lua_State luaState); + + [BizImport(cc)] + public abstract void lua_pushvalue(lua_State luaState, int index); + + [BizImport(cc)] + public abstract int lua_rawequal(lua_State luaState, int index1, int index2); + + [BizImport(cc)] + public abstract int lua_rawget(lua_State luaState, int index); + + [BizImport(cc)] + public abstract int lua_rawgeti(lua_State luaState, int index, lua_Integer n); + + [BizImport(cc)] + public abstract int lua_rawgetp(lua_State luaState, int index, voidptr_t p); + + [BizImport(cc)] + public abstract size_t lua_rawlen(lua_State luaState, int index); + + [BizImport(cc)] + public abstract void lua_rawset(lua_State luaState, int index); + + [BizImport(cc)] + public abstract void lua_rawseti(lua_State luaState, int index, lua_Integer i); + + [BizImport(cc)] + public abstract void lua_rawsetp(lua_State luaState, int index, voidptr_t p); + + [BizImport(cc)] + public abstract int lua_resetthread(lua_State luaState); + + [BizImport(cc)] + public abstract int lua_resume(lua_State luaState, lua_State from, int nargs, out int results); + + [BizImport(cc)] + public abstract void lua_rotate(lua_State luaState, int index, int n); + + [BizImport(cc)] + public abstract void lua_setallocf(lua_State luaState, lua_Alloc f, voidptr_t ud); + + [BizImport(cc)] + public abstract void lua_setfield(lua_State luaState, int index, string key); + + [BizImport(cc)] + public abstract void lua_setglobal(lua_State luaState, string key); + + [BizImport(cc)] + public abstract void lua_sethook(lua_State luaState, lua_Hook f, int mask, int count); + + [BizImport(cc)] + public abstract void lua_seti(lua_State luaState, int index, long n); + + [BizImport(cc)] + public abstract void lua_setiuservalue(lua_State luaState, int index, int n); + + [BizImport(cc)] + public abstract charptr_t lua_setlocal(lua_State luaState, lua_Debug ar, int n); + + [BizImport(cc)] + public abstract void lua_setmetatable(lua_State luaState, int objIndex); + + [BizImport(cc)] + public abstract void lua_settable(lua_State luaState, int index); + + [BizImport(cc)] + public abstract void lua_settop(lua_State luaState, int newTop); + + [BizImport(cc)] + public abstract charptr_t lua_setupvalue(lua_State luaState, int funcIndex, int n); + + [BizImport(cc)] + public abstract void lua_setwarnf(lua_State luaState, lua_WarnFunction warningFunctionPtr, voidptr_t ud); + + [BizImport(cc)] + public abstract int lua_status(lua_State luaState); + + [BizImport(cc)] + public abstract size_t lua_stringtonumber(lua_State luaState, string s); + + [BizImport(cc)] + public abstract int lua_toboolean(lua_State luaState, int index); + + [BizImport(cc)] + public abstract lua_CFunction lua_tocfunction(lua_State luaState, int index); + + [BizImport(cc)] + public abstract lua_CFunction lua_toclose(lua_State luaState, int index); + + [BizImport(cc)] + public abstract lua_Integer lua_tointegerx(lua_State luaState, int index, out int isNum); + + [BizImport(cc)] + public abstract charptr_t lua_tolstring(lua_State luaState, int index, out size_t strLen); + + [BizImport(cc)] + public abstract lua_Number lua_tonumberx(lua_State luaState, int index, out int isNum); + + [BizImport(cc)] + public abstract voidptr_t lua_topointer(lua_State luaState, int index); + + [BizImport(cc)] + public abstract lua_State lua_tothread(lua_State luaState, int index); + + [BizImport(cc)] + public abstract voidptr_t lua_touserdata(lua_State luaState, int index); + + [BizImport(cc)] + public abstract int lua_type(lua_State luaState, int index); + + [BizImport(cc)] + public abstract charptr_t lua_typename(lua_State luaState, int type); + + [BizImport(cc)] + public abstract voidptr_t lua_upvalueid(lua_State luaState, int funcIndex, int n); + + [BizImport(cc)] + public abstract void lua_upvaluejoin(lua_State luaState, int funcIndex1, int n1, int funcIndex2, int n2); + + [BizImport(cc)] + public abstract lua_Number lua_version(lua_State luaState); + + [BizImport(cc)] + public abstract void lua_warning(lua_State luaState, string msg, int tocont); + + [BizImport(cc)] + public abstract void lua_xmove(lua_State from, lua_State to, int n); + + [BizImport(cc)] + public abstract int lua_yieldk(lua_State luaState, + int nresults, + lua_KContext ctx, + lua_KFunction k); + + [BizImport(cc)] + public abstract int luaL_argerror(lua_State luaState, int arg, string message); + + [BizImport(cc)] + public abstract int luaL_callmeta(lua_State luaState, int obj, string e); + + [BizImport(cc)] + public abstract void luaL_checkany(lua_State luaState, int arg); + + [BizImport(cc)] + public abstract lua_Integer luaL_checkinteger(lua_State luaState, int arg); + + [BizImport(cc)] + public abstract charptr_t luaL_checklstring(lua_State luaState, int arg, out size_t len); + + [BizImport(cc)] + public abstract lua_Number luaL_checknumber(lua_State luaState, int arg); + + [BizImport(cc, Compatibility = true)] + public abstract int luaL_checkoption(lua_State luaState, int arg, string def, string[] list); + + [BizImport(cc)] + public abstract void luaL_checkstack(lua_State luaState, int sz, string message); + + [BizImport(cc)] + public abstract void luaL_checktype(lua_State luaState, int arg, int type); + + [BizImport(cc)] + public abstract voidptr_t luaL_checkudata(lua_State luaState, int arg, string tName); + + [BizImport(cc)] + public abstract void luaL_checkversion_(lua_State luaState, lua_Number ver, size_t sz); + + [BizImport(cc)] + public abstract int luaL_error(lua_State luaState, string message); + + [BizImport(cc)] + public abstract int luaL_execresult(lua_State luaState, int stat); + + [BizImport(cc)] + public abstract int luaL_fileresult(lua_State luaState, int stat, string fileName); + + [BizImport(cc)] + public abstract int luaL_getmetafield(lua_State luaState, int obj, string e); + + [BizImport(cc)] + public abstract int luaL_getsubtable(lua_State luaState, int index, string name); + + [BizImport(cc)] + public abstract lua_Integer luaL_len(lua_State luaState, int index); + + [BizImport(cc)] + public abstract int luaL_loadbufferx + (lua_State luaState, + byte[] buff, + size_t sz, + string name, + string mode); + + [BizImport(cc)] + public abstract int luaL_loadfilex(lua_State luaState, string name, string mode); + + [BizImport(cc)] + public abstract int luaL_newmetatable(lua_State luaState, string name); + + [BizImport(cc)] + public abstract lua_State luaL_newstate(); + + [BizImport(cc)] + public abstract void luaL_openlibs(lua_State luaState); + + [BizImport(cc)] + public abstract lua_Integer luaL_optinteger(lua_State luaState, int arg, lua_Integer d); + + [BizImport(cc)] + public abstract lua_Number luaL_optnumber(lua_State luaState, int arg, lua_Number d); + + [BizImport(cc)] + public abstract int luaL_ref(lua_State luaState, int registryIndex); + + [BizImport(cc)] + public abstract void luaL_requiref(lua_State luaState, string moduleName, lua_CFunction openFunction, int global); + + [BizImport(cc, Compatibility = true)] + public abstract void luaL_setfuncs(lua_State luaState, [In] LuaRegister[] luaReg, int numUp); + + [BizImport(cc)] + public abstract void luaL_setmetatable(lua_State luaState, string tName); + + [BizImport(cc)] + public abstract voidptr_t luaL_testudata(lua_State luaState, int arg, string tName); + + [BizImport(cc)] + public abstract charptr_t luaL_tolstring(lua_State luaState, int index, out size_t len); + + [BizImport(cc)] + public abstract charptr_t luaL_traceback + (lua_State luaState, + lua_State luaState2, + string message, + int level); + + [BizImport(cc)] + public abstract int luaL_typeerror(lua_State luaState, int arg, string typeName); + + [BizImport(cc)] + public abstract void luaL_unref(lua_State luaState, int registryIndex, int reference); + + [BizImport(cc)] + public abstract void luaL_where(lua_State luaState, int level); + +#pragma warning restore SA1121 // Use built-in type alias + } +} diff --git a/ExternalProjects/NLua/src/ObjectTranslator.cs b/ExternalProjects/NLua/src/ObjectTranslator.cs new file mode 100644 index 0000000000..f244f27d30 --- /dev/null +++ b/ExternalProjects/NLua/src/ObjectTranslator.cs @@ -0,0 +1,1162 @@ +using System; +using System.IO; +using System.Reflection; +using System.Collections.Generic; +using System.Collections.Concurrent; +using System.Runtime.InteropServices; + +using NLua.Method; +using NLua.Exceptions; +using NLua.Extensions; + +namespace NLua +{ + public class ObjectTranslator + { + // Compare cache entries by exact reference to avoid unwanted aliases + private class ReferenceComparer : IEqualityComparer + { + public new bool Equals(object x, object y) + { + if (x != null && y != null && x.GetType() == y.GetType() && x.GetType().IsValueType && y.GetType().IsValueType) + return x.Equals(y); // Special case for boxed value types + return ReferenceEquals(x, y); + } + + public int GetHashCode(object obj) + { + return obj.GetHashCode(); + } + } + + private static readonly LuaNativeFunction _registerTableFunction = RegisterTable; + private static readonly LuaNativeFunction _unregisterTableFunction = UnregisterTable; + private static readonly LuaNativeFunction _getMethodSigFunction = GetMethodSignature; + private static readonly LuaNativeFunction _getConstructorSigFunction = GetConstructorSignature; + private static readonly LuaNativeFunction _importTypeFunction = ImportType; + private static readonly LuaNativeFunction _loadAssemblyFunction = LoadAssembly; + private static readonly LuaNativeFunction _ctypeFunction = CType; + private static readonly LuaNativeFunction _enumFromIntFunction = EnumFromInt; + + // object to object # + internal readonly Dictionary _objectsBackMap = new Dictionary(new ReferenceComparer()); + // object # to object (FIXME - it should be possible to get object address as an object #) + internal readonly Dictionary _objects = new Dictionary(); + + internal readonly ConcurrentQueue finalizedReferences = new ConcurrentQueue(); + + internal EventHandlerContainer PendingEvents = new EventHandlerContainer(); + internal MetaFunctions metaFunctions; + internal List assemblies; + internal CheckType typeChecker; + internal Lua interpreter; + + /// + /// We want to ensure that objects always have a unique ID + /// + internal int _nextObj; + + public MetaFunctions MetaFunctionsInstance => metaFunctions; + public Lua Interpreter => interpreter; + public IntPtr Tag => _tagPtr; + + internal readonly IntPtr _tagPtr; + + public ObjectTranslator(Lua interpreter, LuaState luaState) + { + _tagPtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(int))); + this.interpreter = interpreter; + typeChecker = new CheckType(this); + metaFunctions = new MetaFunctions(this); + assemblies = new List(); + + 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(LuaState luaState) + { + luaState.PushString("luaNet_objects"); + luaState.NewTable(); + luaState.NewTable(); + luaState.PushString("__mode"); + luaState.PushString("v"); + luaState.SetTable(-3); + luaState.SetMetaTable(-2); + luaState.SetTable((int)LuaRegistry.Index); + } + + /// + /// Registers the indexing function of CLR objects + /// passed to Lua + /// + private void CreateIndexingMetaFunction(LuaState luaState) + { + luaState.PushString("luaNet_indexfunction"); + luaState.DoString(MetaFunctions.LuaIndexFunction); + luaState.RawSet(LuaRegistry.Index); + } + + /// + /// Creates the metatable for superclasses (the base + /// field of registered tables) + /// + /// + private void CreateBaseClassMetatable(LuaState luaState) + { + luaState.NewMetaTable("luaNet_searchbase"); + luaState.PushString("__gc"); + luaState.PushCFunction(MetaFunctions.GcFunction); + luaState.SetTable(-3); + luaState.PushString("__tostring"); + luaState.PushCFunction(MetaFunctions.ToStringFunction); + luaState.SetTable(-3); + luaState.PushString("__index"); + luaState.PushCFunction(MetaFunctions.BaseIndexFunction); + luaState.SetTable(-3); + luaState.PushString("__newindex"); + luaState.PushCFunction(MetaFunctions.NewIndexFunction); + luaState.SetTable(-3); + luaState.SetTop(-2); + } + + /// + /// Creates the metatable for type references + /// + private void CreateClassMetatable(LuaState luaState) + { + luaState.NewMetaTable("luaNet_class"); + luaState.PushString("__gc"); + luaState.PushCFunction(MetaFunctions.GcFunction); + luaState.SetTable(-3); + luaState.PushString("__tostring"); + luaState.PushCFunction(MetaFunctions.ToStringFunction); + luaState.SetTable(-3); + luaState.PushString("__index"); + luaState.PushCFunction(MetaFunctions.ClassIndexFunction); + luaState.SetTable(-3); + luaState.PushString("__newindex"); + luaState.PushCFunction(MetaFunctions.ClassNewIndexFunction); + luaState.SetTable(-3); + luaState.PushString("__call"); + luaState.PushCFunction(MetaFunctions.CallConstructorFunction); + luaState.SetTable(-3); + luaState.SetTop(-2); + } + + /// + /// Registers the global functions used by NLua + /// + private void SetGlobalFunctions(LuaState luaState) + { + luaState.PushCFunction(MetaFunctions.IndexFunction); + luaState.SetGlobal("get_object_member"); + luaState.PushCFunction(_importTypeFunction); + luaState.SetGlobal("import_type"); + luaState.PushCFunction(_loadAssemblyFunction); + luaState.SetGlobal("load_assembly"); + luaState.PushCFunction(_registerTableFunction); + luaState.SetGlobal("make_object"); + luaState.PushCFunction(_unregisterTableFunction); + luaState.SetGlobal("free_object"); + luaState.PushCFunction(_getMethodSigFunction); + luaState.SetGlobal("get_method_bysig"); + luaState.PushCFunction(_getConstructorSigFunction); + luaState.SetGlobal("get_constructor_bysig"); + luaState.PushCFunction(_ctypeFunction); + luaState.SetGlobal("ctype"); + luaState.PushCFunction(_enumFromIntFunction); + luaState.SetGlobal("enum"); + } + + /// + /// Creates the metatable for delegates + /// + private void CreateFunctionMetatable(LuaState luaState) + { + luaState.NewMetaTable("luaNet_function"); + luaState.PushString("__gc"); + luaState.PushCFunction(MetaFunctions.GcFunction); + luaState.SetTable(-3); + luaState.PushString("__call"); + luaState.PushCFunction(MetaFunctions.ExecuteDelegateFunction); + luaState.SetTable(-3); + luaState.SetTop(-2); + } + + /// + /// Passes errors (argument e) to the Lua interpreter + /// + internal void ThrowError(LuaState luaState, object e) + { + // We use this to remove anything pushed by luaL_where + int oldTop = luaState.GetTop(); + + // 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 + luaState.Where(1); + var curlev = PopValues(luaState, oldTop); + + // Determine the position in the script where the exception was triggered + string errLocation = string.Empty; + + 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 + if (interpreter.UseTraceback) + message += Environment.NewLine + interpreter.GetDebugTraceback(); + e = new LuaScriptException(message, errLocation); + } + else + { + var ex = e as Exception; + + if (ex != null) + { + // Wrap generic .NET exception as an InnerException and store the error location + if (interpreter.UseTraceback) ex.Data["Traceback"] = interpreter.GetDebugTraceback(); + e = new LuaScriptException(ex, errLocation); + } + } + + Push(luaState, e); + } + + /// + /// Implementation of load_assembly. Throws an error + /// if the assembly is not found. + /// + private static int LoadAssembly(IntPtr luaState) + { + var state = LuaState.FromIntPtr(luaState); + var translator = ObjectTranslatorPool.Instance.Find(state); + int result = translator.LoadAssemblyInternal(state); + var exception = translator.GetObject(state, -1) as LuaScriptException; + + if (exception != null) + return state.Error(); + return result; + } + + private int LoadAssemblyInternal(LuaState luaState) + { + try + { + string assemblyName = luaState.ToString(1, false); + Assembly assembly = null; + Exception exception = null; + + try + { + assembly = Assembly.Load(assemblyName); + } + catch (BadImageFormatException) + { + // The assemblyName was invalid. It is most likely a path. + } + catch (FileNotFoundException e) + { + exception = e; + } + + if (assembly == null) + { + try + { + assembly = Assembly.Load(AssemblyName.GetAssemblyName(assemblyName)); + } + catch (FileNotFoundException e) + { + exception = e; + } + if (assembly == null) + { + AssemblyName mscor = assemblies[0].GetName(); + AssemblyName name = new AssemblyName(); + name.Name = assemblyName; + name.CultureInfo = mscor.CultureInfo; + name.Version = mscor.Version; + name.SetPublicKeyToken(mscor.GetPublicKeyToken()); + name.SetPublicKey(mscor.GetPublicKey()); + assembly = Assembly.Load(name); + + if (assembly != null) + exception = null; + } + if (exception != null) + { + ThrowError(luaState, exception); + return 1; + } + } + if (assembly != null && !assemblies.Contains(assembly)) + assemblies.Add(assembly); + } + catch (Exception e) + { + ThrowError(luaState, e); + return 1; + } + return 0; + } + + internal Type FindType(string className) + { + foreach (var assembly in assemblies) + { + var klass = assembly.GetType(className); + + if (klass != null) + return klass; + } + return null; + } + + public bool TryGetExtensionMethod(Type type, string name, out MethodInfo method) + { + method = GetExtensionMethod(type, name); + return method != null; + } + + public MethodInfo GetExtensionMethod(Type type, string name) + { + return type.GetExtensionMethod(name, assemblies); + } + + /// + /// Implementation of import_type. Returns nil if the + /// type is not found. + /// + private static int ImportType(IntPtr luaState) + { + var state = LuaState.FromIntPtr(luaState); + var translator = ObjectTranslatorPool.Instance.Find(state); + return translator.ImportTypeInternal(state); + } + + private int ImportTypeInternal(LuaState luaState) + { + string className = luaState.ToString(1, false); + var klass = FindType(className); + + if (klass != null) + PushType(luaState, klass); + else + luaState.PushNil(); + + 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 static int RegisterTable(IntPtr luaState) + { + var state = LuaState.FromIntPtr(luaState); + var translator = ObjectTranslatorPool.Instance.Find(state); + int result = translator.RegisterTableInternal(state); + var exception = translator.GetObject(state, -1) as LuaScriptException; + + if (exception != null) + return state.Error(); + return result; + } + + private int RegisterTableInternal(LuaState luaState) + { + if (luaState.Type(1) != LuaType.Table) + { + ThrowError(luaState, "register_table: first arg is not a table"); + return 1; + } + + LuaTable luaTable = GetTable(luaState, 1); + string superclassName = luaState.ToString(2, false); + + if (string.IsNullOrEmpty(superclassName)) + { + ThrowError(luaState, "register_table: superclass name can not be null"); + return 1; + } + + var klass = FindType(superclassName); + + if (klass == null) + { + ThrowError(luaState, "register_table: can not find superclass '" + superclassName + "'"); + return 1; + } + + // 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"); + luaState.NewTable(); + luaState.PushString("__index"); + luaState.PushCopy(-3); + luaState.SetTable(-3); + luaState.PushString("__newindex"); + luaState.PushCopy(-3); + luaState.SetTable(-3); + luaState.SetMetaTable(1); + + // Pushes the object again, this time as the base field + // of the table and with the luaNet_searchbase metatable + luaState.PushString("base"); + int index = AddObject(obj); + PushNewObject(luaState, obj, index, "luaNet_searchbase"); + luaState.RawSet(1); + + return 0; + } + + /// + /// Implementation of free_object. Clears the metatable and the + /// base field, freeing the created object for garbage-collection + /// + private static int UnregisterTable(IntPtr luaState) + { + var state = LuaState.FromIntPtr(luaState); + var translator = ObjectTranslatorPool.Instance.Find(state); + int result = translator.UnregisterTableInternal(state); + var exception = translator.GetObject(state, -1) as LuaScriptException; + + if (exception != null) + return state.Error(); + return result; + } + + private int UnregisterTableInternal(LuaState luaState) + { + + if (!luaState.GetMetaTable(1)) + { + ThrowError(luaState, "unregister_table: arg is not valid table"); + return 1; + } + + luaState.PushString("__index"); + luaState.GetTable(-2); + object obj = GetRawNetObject(luaState, -1); + + if (obj == null) + { + ThrowError(luaState, "unregister_table: arg is not valid table"); + return 1; + } + + var luaTableField = obj.GetType().GetField("__luaInterface_luaTable"); + + if (luaTableField == null) + { + ThrowError(luaState, "unregister_table: arg is not valid table"); + return 1; + } + + // ReSharper disable once PossibleNullReferenceException + luaTableField.SetValue(obj, null); + luaState.PushNil(); + luaState.SetMetaTable(1); + luaState.PushString("base"); + luaState.PushNil(); + luaState.SetTable(1); + + return 0; + } + + /// + /// Implementation of get_method_bysig. Returns nil + /// if no matching method is not found. + /// + private static int GetMethodSignature(IntPtr luaState) + { + var state = LuaState.FromIntPtr(luaState); + var translator = ObjectTranslatorPool.Instance.Find(state); + int result = translator.GetMethodSignatureInternal(state); + var exception = translator.GetObject(state, -1) as LuaScriptException; + + if (exception != null) + return state.Error(); + return result; + } + + private int GetMethodSignatureInternal(LuaState luaState) //-V3009 + { + ProxyType klass; + object target; + int udata = luaState.CheckUObject(1, "luaNet_class"); + + if (udata != -1) + { + klass = (ProxyType)_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"); + return 1; + } + + klass = new ProxyType(target.GetType()); + } + + string methodName = luaState.ToString(2, false); + var signature = new Type[luaState.GetTop() - 2]; + + for (int i = 0; i < signature.Length; i++) + signature[i] = FindType(luaState.ToString(i + 3, false)); + + try + { + var method = klass.GetMethod(methodName, BindingFlags.Public | BindingFlags.Static | + BindingFlags.Instance, signature); + var wrapper = new LuaMethodWrapper(this, target, klass, method); + LuaNativeFunction invokeDelegate = wrapper.InvokeFunction; + PushFunction(luaState, invokeDelegate); + } + catch (Exception e) + { + ThrowError(luaState, e); + } + + return 1; + } + + /// + /// Implementation of get_constructor_bysig. Returns nil + /// if no matching constructor is found. + /// + /// + /// + private static int GetConstructorSignature(IntPtr luaState) + { + var state = LuaState.FromIntPtr(luaState); + var translator = ObjectTranslatorPool.Instance.Find(state); + int result = translator.GetConstructorSignatureInternal(state); + var exception = translator.GetObject(state, -1) as LuaScriptException; + + if (exception != null) + return state.Error(); + return result; + } + + private int GetConstructorSignatureInternal(LuaState luaState) //-V3009 + { + ProxyType klass = null; + int udata = luaState.CheckUObject(1, "luaNet_class"); + + if (udata != -1) + klass = (ProxyType)_objects[udata]; + + if (klass == null) + { + ThrowError(luaState, "get_constructor_bysig: first arg is invalid type reference"); + return 1; + } + + var signature = new Type[luaState.GetTop() - 1]; + + for (int i = 0; i < signature.Length; i++) + signature[i] = FindType(luaState.ToString(i + 2, false)); + + try + { + ConstructorInfo constructor = klass.UnderlyingSystemType.GetConstructor(signature); + var wrapper = new LuaMethodWrapper(this, null, klass, constructor); + var invokeDelegate = wrapper.InvokeFunction; + PushFunction(luaState, invokeDelegate); + } + catch (Exception e) + { + ThrowError(luaState, e); + } + return 1; + } + + /// + /// Pushes a type reference into the stack + /// + internal void PushType(LuaState luaState, Type t) + { + PushObject(luaState, new ProxyType(t), "luaNet_class"); + } + + /// + /// Pushes a delegate into the stack + /// + internal void PushFunction(LuaState luaState, LuaNativeFunction func) + { + PushObject(luaState, func, "luaNet_function"); + } + + /// + /// Pushes a CLR object into the Lua stack as an userdata + /// with the provided metatable + /// + internal void PushObject(LuaState luaState, object o, string metatable) + { + int index = -1; + + // Pushes nil + if (o == null) + { + luaState.PushNil(); + return; + } + + // Object already in the list of Lua objects? Push the stored reference. + bool found = (!o.GetType().IsValueType || o.GetType().IsEnum) && _objectsBackMap.TryGetValue(o, out index); + + if (found) + { + luaState.GetMetaTable("luaNet_objects"); + luaState.RawGetInteger(-1, index); + + // Note: starting with lua5.1 the garbage collector may remove weak reference items (such as our luaNet_objects values) when the initial GC sweep + // occurs, but the actual call of the __gc finalizer for that object may not happen until a little while later. During that window we might call + // this routine and find the element missing from luaNet_objects, but collectObject() has not yet been called. In that case, we go ahead and call collect + // object here + // did we find a non nil object in our table? if not, we need to call collect object + var type = luaState.Type(-1); + if (type != LuaType.Nil) + { + luaState.Remove(-2); // drop the metatable - we're going to leave our object on the stack + return; + } + + // MetaFunctions.dumpStack(this, luaState); + luaState.Remove(-1); // remove the nil object value + luaState.Remove(-1); // remove the metatable + CollectObject(o, index); // Remove from both our tables and fall out to get a new ID + } + + index = AddObject(o); + PushNewObject(luaState, o, index, metatable); + } + + /// + /// Pushes a new object into the Lua stack with the provided + /// metatable + /// + private void PushNewObject(LuaState luaState, object o, int index, string metatable) + { + if (metatable == "luaNet_metatable") + { + // Gets or creates the metatable for the object's type + luaState.GetMetaTable(o.GetType().AssemblyQualifiedName); + + if (luaState.IsNil(-1)) + { + luaState.SetTop(-2); + luaState.NewMetaTable(o.GetType().AssemblyQualifiedName); + luaState.PushString("cache"); + luaState.NewTable(); + luaState.RawSet(-3); + luaState.PushLightUserData(_tagPtr); + luaState.PushNumber(1); + luaState.RawSet(-3); + luaState.PushString("__index"); + luaState.PushString("luaNet_indexfunction"); + luaState.RawGet(LuaRegistry.Index); + luaState.RawSet(-3); + luaState.PushString("__gc"); + luaState.PushCFunction(MetaFunctions.GcFunction); + luaState.RawSet(-3); + luaState.PushString("__tostring"); + luaState.PushCFunction(MetaFunctions.ToStringFunction); + luaState.RawSet(-3); + luaState.PushString("__newindex"); + luaState.PushCFunction(MetaFunctions.NewIndexFunction); + luaState.RawSet(-3); + // Bind C# operator with Lua metamethods (__add, __sub, __mul) + RegisterOperatorsFunctions(luaState, o.GetType()); + RegisterCallMethodForDelegate(luaState, o); + } + } + else + luaState.GetMetaTable(metatable); + + // Stores the object index in the Lua list and pushes the + // index into the Lua stack + luaState.GetMetaTable("luaNet_objects"); + luaState.NewUData(index); + luaState.PushCopy(-3); + luaState.Remove(-4); + luaState.SetMetaTable(-2); + luaState.PushCopy(-1); + luaState.RawSetInteger(-3, index); + luaState.Remove(-2); + } + + internal void RegisterCallMethodForDelegate(LuaState luaState, object o) + { + if (!(o is Delegate)) + return; + + luaState.PushString("__call"); + luaState.PushCFunction(MetaFunctions.CallDelegateFunction); + luaState.RawSet(-3); + } + + internal void RegisterOperatorsFunctions(LuaState luaState, Type type) + { + if (type.HasAdditionOperator()) + { + luaState.PushString("__add"); + luaState.PushCFunction(MetaFunctions.AddFunction); + luaState.RawSet(-3); + } + if (type.HasSubtractionOperator()) + { + luaState.PushString("__sub"); + luaState.PushCFunction(MetaFunctions.SubtractFunction); + luaState.RawSet(-3); + } + if (type.HasMultiplyOperator()) + { + luaState.PushString("__mul"); + luaState.PushCFunction(MetaFunctions.MultiplyFunction); + luaState.RawSet(-3); + } + if (type.HasDivisionOperator()) + { + luaState.PushString("__div"); + luaState.PushCFunction(MetaFunctions.DivisionFunction); + luaState.RawSet(-3); + } + if (type.HasModulusOperator()) + { + luaState.PushString("__mod"); + luaState.PushCFunction(MetaFunctions.ModulosFunction); + luaState.RawSet(-3); + } + if (type.HasUnaryNegationOperator()) + { + luaState.PushString("__unm"); + luaState.PushCFunction(MetaFunctions.UnaryNegationFunction); + luaState.RawSet(-3); + } + if (type.HasEqualityOperator()) + { + luaState.PushString("__eq"); + luaState.PushCFunction(MetaFunctions.EqualFunction); + luaState.RawSet(-3); + } + if (type.HasLessThanOperator()) + { + luaState.PushString("__lt"); + luaState.PushCFunction(MetaFunctions.LessThanFunction); + luaState.RawSet(-3); + } + if (type.HasLessThanOrEqualOperator()) + { + luaState.PushString("__le"); + luaState.PushCFunction(MetaFunctions.LessThanOrEqualFunction); + luaState.RawSet(-3); + } + } + + /// + /// Gets an object from the Lua stack with the desired type, if it matches, otherwise + /// returns null. + /// + internal object GetAsType(LuaState luaState, int stackPos, Type paramType) + { + var extractor = typeChecker.CheckLuaType(luaState, stackPos, paramType); + return extractor != null ? extractor(luaState, stackPos) : null; + } + + /// + /// 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) + CollectObject(o, udata); + } + + /// + /// Given an object reference, remove it from our maps + /// + /// + /// + private void CollectObject(object o, int udata) + { + _objects.Remove(udata); + if (!o.GetType().IsValueType || o.GetType().IsEnum) + _objectsBackMap.Remove(o); + } + + private int AddObject(object obj) + { + // New object: inserts it in the list + int index = _nextObj++; + _objects[index] = obj; + + if (!obj.GetType().IsValueType || obj.GetType().IsEnum) + _objectsBackMap[obj] = index; + + return index; + } + + /// + /// Gets an object from the Lua stack according to its Lua type. + /// + internal object GetObject(LuaState luaState, int index) + { + LuaType type = luaState.Type(index); + + switch (type) + { + case LuaType.Number: + { + if (luaState.IsInteger(index)) + return luaState.ToInteger(index); + + return luaState.ToNumber(index); + } + case LuaType.String: + return luaState.ToString(index, false); + case LuaType.Boolean: + return luaState.ToBoolean(index); + case LuaType.Table: + return GetTable(luaState, index); + case LuaType.Function: + return GetFunction(luaState, index); + case LuaType.UserData: + { + int udata = luaState.ToNetObject(index, Tag); + return udata != -1 ? _objects[udata] : GetUserData(luaState, index); + } + case LuaType.Thread: + return GetThread(luaState, index); + default: + return null; + } + } + + /// + /// Gets the table in the index positon of the Lua stack. + /// + internal LuaTable GetTable(LuaState luaState, int index) + { + // Before create new tables, check if there is any finalized object to clean. + CleanFinalizedReferences(luaState); + + luaState.PushCopy(index); + int reference = luaState.Ref(LuaRegistry.Index); + if (reference == -1) + return null; + return new LuaTable(reference, interpreter); + } + + /// + /// Gets the thread in the index positon of the Lua stack. + /// + internal LuaThread GetThread(LuaState luaState, int index) + { + // Before create new tables, check if there is any finalized object to clean. + CleanFinalizedReferences(luaState); + + luaState.PushCopy(index); + int reference = luaState.Ref(LuaRegistry.Index); + if (reference == -1) + return null; + return new LuaThread(reference, interpreter); + } + + /// + /// Gets the userdata in the index positon of the Lua stack. + /// + internal LuaUserData GetUserData(LuaState luaState, int index) + { + // Before create new tables, check if there is any finalized object to clean. + CleanFinalizedReferences(luaState); + + luaState.PushCopy(index); + int reference = luaState.Ref(LuaRegistry.Index); + if (reference == -1) + return null; + return new LuaUserData(reference, interpreter); + } + + /// + /// Gets the function in the index positon of the Lua stack. + /// + internal LuaFunction GetFunction(LuaState luaState, int index) + { + // Before create new tables, check if there is any finalized object to clean. + CleanFinalizedReferences(luaState); + + luaState.PushCopy(index); + int reference = luaState.Ref(LuaRegistry.Index); + if (reference == -1) + return null; + return new LuaFunction(reference, interpreter); + } + + /// + /// Gets the CLR object in the index positon of the Lua stack. Returns + /// delegates as Lua functions. + /// + internal object GetNetObject(LuaState luaState, int index) + { + int idx = luaState.ToNetObject(index, Tag); + return idx != -1 ? _objects[idx] : null; + } + + /// + /// Gets the CLR object in the index position of the Lua stack. Returns + /// delegates as is. + /// + internal object GetRawNetObject(LuaState luaState, int index) + { + int udata = luaState.RawNetObj(index); + return udata != -1 ? _objects[udata] : null; + } + + /// + /// Gets the values from the provided index to + /// the top of the stack and returns them in an array. + /// + internal object[] PopValues(LuaState luaState, int oldTop) + { + int newTop = luaState.GetTop(); + + if (oldTop == newTop) + return Array.Empty(); + + var returnValues = new List(); + for (int i = oldTop + 1; i <= newTop; i++) + returnValues.Add(GetObject(luaState, i)); + + luaState.SetTop(oldTop); + return returnValues.ToArray(); + } + + /// + /// Gets the values from the provided index to + /// the top of the stack and returns them in an array, casting + /// them to the provided types. + /// + internal object[] PopValues(LuaState luaState, int oldTop, Type[] popTypes) + { + int newTop = luaState.GetTop(); + + if (oldTop == newTop) + return Array.Empty(); + + int iTypes; + var returnValues = new List(); + + if (popTypes[0] == typeof(void)) + iTypes = 1; + else + iTypes = 0; + + for (int i = oldTop + 1; i <= newTop; i++) + { + returnValues.Add(GetAsType(luaState, i, popTypes[iTypes])); + iTypes++; + } + + luaState.SetTop(oldTop); + return returnValues.ToArray(); + } + + // The following line doesn't work for remoting proxies - they always return a match for 'is' + // else if (o is ILuaGeneratedType) + private static bool IsILua(object o) + { + if (o is ILuaGeneratedType) + { + // Make sure we are _really_ ILuaGenerated + var typ = o.GetType(); + return typ.GetInterface("ILuaGeneratedType", true) != null; + } + return false; + } + + /// + /// Pushes the object into the Lua stack according to its type. + /// + internal void Push(LuaState luaState, object o) + { + if (o == null) + luaState.PushNil(); + else if (o is sbyte sb) + luaState.PushInteger(sb); + else if(o is byte bt) + luaState.PushInteger(bt); + else if(o is short s) + luaState.PushInteger(s); + else if (o is ushort us) + luaState.PushInteger(us); + else if (o is int i) + luaState.PushInteger(i); + else if (o is uint ui) + luaState.PushInteger(ui); + else if (o is long l) + luaState.PushInteger(l); + else if (o is ulong ul) + luaState.PushInteger((long)ul); + else if (o is char ch) + luaState.PushInteger(ch); + else if (o is float fl) + luaState.PushNumber(fl); + else if(o is decimal dc) + luaState.PushNumber((double)dc); + else if(o is double db) + luaState.PushNumber(db); + else if (o is string str) + luaState.PushString(str); + else if (o is bool b) + luaState.PushBoolean(b); + else if (IsILua(o)) + ((ILuaGeneratedType)o).LuaInterfaceGetLuaTable().Push(luaState); + else if (o is LuaTable table) + table.Push(luaState); + else if (o is LuaThread thread) + thread.Push(luaState); + else if (o is LuaNativeFunction nativeFunction) + PushFunction(luaState, nativeFunction); + else if (o is LuaFunction luaFunction) + luaFunction.Push(luaState); + else if (o is LuaUserData userData) + userData.Push(luaState); + else + PushObject(luaState, o, "luaNet_metatable"); + } + + /// + /// Checks if the method matches the arguments in the Lua stack, getting + /// the arguments if it does. + /// + internal bool MatchParameters(LuaState luaState, MethodBase method, MethodCache methodCache, int skipParam) + { + return metaFunctions.MatchParameters(luaState, method, methodCache, skipParam); + } + + internal Array TableToArray(LuaState luaState, ExtractValue extractValue, Type paramArrayType, int startIndex, int count) + { + return metaFunctions.TableToArray(luaState, extractValue, paramArrayType, ref startIndex, count); + } + + private Type TypeOf(LuaState luaState, int idx) + { + int udata = luaState.CheckUObject(idx, "luaNet_class"); + if (udata == -1) + return null; + + var pt = (ProxyType)_objects[udata]; + return pt.UnderlyingSystemType; + } + + internal static int PushError(LuaState luaState, string msg) + { + luaState.PushNil(); + luaState.PushString(msg); + return 2; + } + + private static int CType(IntPtr luaState) + { + var state = LuaState.FromIntPtr(luaState); + var translator = ObjectTranslatorPool.Instance.Find(state); + return translator.CTypeInternal(state); + } + + internal int CTypeInternal(LuaState luaState) + { + Type t = TypeOf(luaState,1); + if (t == null) + return PushError(luaState, "Not a CLR Class"); + + PushObject(luaState, t, "luaNet_metatable"); + return 1; + } + + private static int EnumFromInt(IntPtr luaState) + { + var state = LuaState.FromIntPtr(luaState); + var translator = ObjectTranslatorPool.Instance.Find(state); + return translator.EnumFromIntInternal(state); + } + + internal int EnumFromIntInternal(LuaState luaState) + { + Type t = TypeOf(luaState, 1); + if (t == null || !t.IsEnum) + return PushError(luaState, "Not an Enum."); + + object res = null; + LuaType lt = luaState.Type(2); + if (lt == LuaType.Number) + { + int ival = (int)luaState.ToNumber(2); + res = Enum.ToObject(t, ival); + } + else if (lt == LuaType.String) + { + string sflags = luaState.ToString(2, false); + string err = null; + try + { + res = Enum.Parse(t, sflags, true); + } + catch (ArgumentException e) + { + err = e.Message; + } + if (err != null) + return PushError(luaState, err); + } + else + { + return PushError(luaState, "Second argument must be a integer or a string."); + } + PushObject(luaState, res, "luaNet_metatable"); + return 1; + } + + internal void AddFinalizedReference(int reference) + { + finalizedReferences.Enqueue(reference); + } + + internal void CleanFinalizedReferences(LuaState state) + { + if (finalizedReferences.Count == 0) + return; + + int reference; + + while (finalizedReferences.TryDequeue(out reference)) + state.Unref(LuaRegistry.Index, reference); + } + } +} diff --git a/ExternalProjects/NLua/src/ObjectTranslatorPool.cs b/ExternalProjects/NLua/src/ObjectTranslatorPool.cs new file mode 100644 index 0000000000..d254fb8eb4 --- /dev/null +++ b/ExternalProjects/NLua/src/ObjectTranslatorPool.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Concurrent; + +namespace NLua +{ + internal class ObjectTranslatorPool + { + private static volatile ObjectTranslatorPool _instance = new ObjectTranslatorPool(); + + private ConcurrentDictionary translators = new ConcurrentDictionary(); + + public static ObjectTranslatorPool Instance => _instance; + + + public void Add(LuaState luaState, ObjectTranslator translator) + { + if(!translators.TryAdd(luaState, translator)) + throw new ArgumentException("An item with the same key has already been added. ", nameof(luaState)); + } + + public ObjectTranslator Find(LuaState luaState) + { + ObjectTranslator translator; + + if(!translators.TryGetValue(luaState, out translator)) + { + LuaState main = luaState.MainThread; + + if (!translators.TryGetValue(main, out translator)) + throw new Exception("Invalid luaState, couldn't find ObjectTranslator"); + } + return translator; + } + + public void Remove(LuaState luaState) + { + ObjectTranslator translator; + translators.TryRemove(luaState, out translator); + } + } +} + diff --git a/ExternalProjects/NLua/src/Properties/AssemblyInfo.cs b/ExternalProjects/NLua/src/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..b77be2948e --- /dev/null +++ b/ExternalProjects/NLua/src/Properties/AssemblyInfo.cs @@ -0,0 +1,19 @@ +using System.Reflection; + +// Information about this assembly is defined by the following attributes. +// Change them to the values specific to your project. + +[assembly: AssemblyTitle ("NLua (.NET Standard)")] + +[assembly: AssemblyDescription ("NLua library")] +[assembly: AssemblyCompany ("NLua.org")] +[assembly: AssemblyProduct ("NLua")] +[assembly: AssemblyCopyright ("Copyright © Vinicius Jarina 2021")] +[assembly: AssemblyCulture ("")] + + +[assembly: AssemblyVersion("1.4.1.0")] +[assembly: AssemblyInformationalVersion("1.0.7+Branch.main.Sha.80a328a64f12ed9032a0f14a75e6ecad967514d0")] +[assembly: AssemblyFileVersion("1.4.1.0")] + + diff --git a/ExternalProjects/NLua/src/ProxyType.cs b/ExternalProjects/NLua/src/ProxyType.cs new file mode 100644 index 0000000000..f1cb8f76de --- /dev/null +++ b/ExternalProjects/NLua/src/ProxyType.cs @@ -0,0 +1,50 @@ +using System; +using System.Reflection; + +namespace NLua +{ + public class ProxyType + { + private readonly Type _proxy; + + public ProxyType(Type proxy) + { + _proxy = proxy; + } + + /// + /// Provide human readable short hand for this proxy object + /// + /// + public override string ToString() + { + return "ProxyType(" + UnderlyingSystemType + ")"; + } + + public Type UnderlyingSystemType => _proxy; + + public override bool Equals(object obj) + { + if (obj is Type) + return _proxy == (Type)obj; + if (obj is ProxyType) + return _proxy == ((ProxyType)obj).UnderlyingSystemType; + return _proxy.Equals(obj); + } + + public override int GetHashCode() + { + return _proxy.GetHashCode(); + } + + public MemberInfo[] GetMember(string name, BindingFlags bindingAttr) + { + return _proxy.GetMember(name, bindingAttr); + } + + public MethodInfo GetMethod(string name, BindingFlags bindingAttr, Type[] signature) + { + return _proxy.GetMethod(name, bindingAttr, null, signature, null); + } + } +} \ No newline at end of file diff --git a/References/KeraLua.dll b/References/KeraLua.dll deleted file mode 100644 index e1aa1f948e..0000000000 Binary files a/References/KeraLua.dll and /dev/null differ diff --git a/References/NLua.deps.json b/References/NLua.deps.json index 683188bdff..d5263f3505 100644 --- a/References/NLua.deps.json +++ b/References/NLua.deps.json @@ -9,35 +9,79 @@ ".NETStandard,Version=v2.0/": { "NLua/1.0.0": { "dependencies": { - "KeraLua": "1.3.2", - "Microsoft.SourceLink.GitHub": "1.1.0", - "NETStandard.Library": "2.0.3" + "DotNetAnalyzers.DocumentationAnalyzers": "1.0.0-beta.59", + "Menees.Analyzers": "3.0.8", + "Meziantou.Analyzer": "1.0.707", + "NETStandard.Library": "2.0.3", + "Nullable": "1.3.1", + "StyleCop.Analyzers": "1.2.0-beta.435", + "BizHawk.BizInvoke": "1.0.0.0", + "BizHawk.Common": "1.0.0.0" }, "runtime": { "NLua.dll": {} } }, - "KeraLua/1.3.2": { - "runtime": { - "lib/netstandard2.0/KeraLua.dll": { - "assemblyVersion": "1.3.2.0", - "fileVersion": "1.3.2.0" - } - } - }, - "Microsoft.Build.Tasks.Git/1.1.0": {}, - "Microsoft.NETCore.Platforms/1.1.0": {}, - "Microsoft.SourceLink.Common/1.1.0": {}, - "Microsoft.SourceLink.GitHub/1.1.0": { + "DotNetAnalyzers.DocumentationAnalyzers/1.0.0-beta.59": { "dependencies": { - "Microsoft.Build.Tasks.Git": "1.1.0", - "Microsoft.SourceLink.Common": "1.1.0" + "DotNetAnalyzers.DocumentationAnalyzers.Unstable": "1.0.0.59" } }, + "DotNetAnalyzers.DocumentationAnalyzers.Unstable/1.0.0.59": {}, + "Menees.Analyzers/3.0.8": {}, + "Meziantou.Analyzer/1.0.707": {}, + "Microsoft.NETCore.Platforms/1.1.0": {}, "NETStandard.Library/2.0.3": { "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0" } + }, + "Nullable/1.3.1": {}, + "StyleCop.Analyzers/1.2.0-beta.435": { + "dependencies": { + "StyleCop.Analyzers.Unstable": "1.2.0.435" + } + }, + "StyleCop.Analyzers.Unstable/1.2.0.435": {}, + "BizHawk.BizInvoke/1.0.0.0": { + "runtime": { + "BizHawk.BizInvoke.dll": { + "assemblyVersion": "1.0.0.0", + "fileVersion": "1.0.0.0" + } + } + }, + "BizHawk.Common/1.0.0.0": { + "runtime": { + "BizHawk.Common.dll": { + "assemblyVersion": "1.0.0.0", + "fileVersion": "1.0.0.0" + } + } + }, + "System.Memory/4.0.1.1": { + "runtime": { + "System.Memory.dll": { + "assemblyVersion": "4.0.1.1", + "fileVersion": "4.6.28619.1" + } + } + }, + "System.Numerics.Vectors/4.1.4.0": { + "runtime": { + "System.Numerics.Vectors.dll": { + "assemblyVersion": "4.1.4.0", + "fileVersion": "4.6.26515.6" + } + } + }, + "System.Buffers/4.0.3.0": { + "runtime": { + "System.Buffers.dll": { + "assemblyVersion": "4.0.3.0", + "fileVersion": "4.6.28619.1" + } + } } } }, @@ -47,19 +91,33 @@ "serviceable": false, "sha512": "" }, - "KeraLua/1.3.2": { + "DotNetAnalyzers.DocumentationAnalyzers/1.0.0-beta.59": { "type": "package", "serviceable": true, - "sha512": "sha512-NXqBDEUC2WbAE43Yx/x+PNfv80SLorUuZLU+2Y71rYSRgmY2vYfmI6+8yINYPRTrySecbaEVRQFlcGJllyYi6g==", - "path": "keralua/1.3.2", - "hashPath": "keralua.1.3.2.nupkg.sha512" + "sha512": "sha512-+cjn5bzar9cqDABF2SUsMEof8yoMXSNdHYdpCVNnj/M/cRACbKYHzYpgilSEvFlHGuolzJJ2WN++/EXwBzSsYQ==", + "path": "dotnetanalyzers.documentationanalyzers/1.0.0-beta.59", + "hashPath": "dotnetanalyzers.documentationanalyzers.1.0.0-beta.59.nupkg.sha512" }, - "Microsoft.Build.Tasks.Git/1.1.0": { + "DotNetAnalyzers.DocumentationAnalyzers.Unstable/1.0.0.59": { "type": "package", "serviceable": true, - "sha512": "sha512-DD84CsvKf+hfDBbYXIvBXeY7JGfbtoIMMjlPGVaKIp6aWoIdL5EJbWvk+skGkM7/Weezj3q1a89nTPAcGlGcpw==", - "path": "microsoft.build.tasks.git/1.1.0", - "hashPath": "microsoft.build.tasks.git.1.1.0.nupkg.sha512" + "sha512": "sha512-ekGYoPsAGTJs5cFbRdzfHKX2n2DzEATZyowGw3QJnozelzmhQ5tGCJhyolN5NWDq1p/GkBPKgRcQ5QGTKxaGrA==", + "path": "dotnetanalyzers.documentationanalyzers.unstable/1.0.0.59", + "hashPath": "dotnetanalyzers.documentationanalyzers.unstable.1.0.0.59.nupkg.sha512" + }, + "Menees.Analyzers/3.0.8": { + "type": "package", + "serviceable": true, + "sha512": "sha512-26WtDVJZe2CEpCqJ16uR0KkCBLwDsHrr4lLSH4JZzUWoYqIQPgpwkh/9VXNJzi5bFeKn/gL0Sz77eFoNaArkhA==", + "path": "menees.analyzers/3.0.8", + "hashPath": "menees.analyzers.3.0.8.nupkg.sha512" + }, + "Meziantou.Analyzer/1.0.707": { + "type": "package", + "serviceable": true, + "sha512": "sha512-8NklUjV3DHVxfyCbPYlwVtBjeNLF23lcS1QUBZt5ri1pxqr2UaVcg/uD+l0ZxwSnwTwTxX6A+Rfn+WksnBsLsg==", + "path": "meziantou.analyzer/1.0.707", + "hashPath": "meziantou.analyzer.1.0.707.nupkg.sha512" }, "Microsoft.NETCore.Platforms/1.1.0": { "type": "package", @@ -68,26 +126,58 @@ "path": "microsoft.netcore.platforms/1.1.0", "hashPath": "microsoft.netcore.platforms.1.1.0.nupkg.sha512" }, - "Microsoft.SourceLink.Common/1.1.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-TyGnSaCanfxunClp2af9jpXY127q7g5tiOv0XN/JGcalyKwYusYp06BUGSmCopg/GhmJJSiR/9PS0suXHCGxtw==", - "path": "microsoft.sourcelink.common/1.1.0", - "hashPath": "microsoft.sourcelink.common.1.1.0.nupkg.sha512" - }, - "Microsoft.SourceLink.GitHub/1.1.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-DD/LF81k5ODHCfPnBCds97N6KKSS1X1NiDcJYUUpiTu5mmuPktZplhYFrAPQPktMBKvNMdwFrwJ7Fqa3tyTIuA==", - "path": "microsoft.sourcelink.github/1.1.0", - "hashPath": "microsoft.sourcelink.github.1.1.0.nupkg.sha512" - }, "NETStandard.Library/2.0.3": { "type": "package", "serviceable": true, "sha512": "sha512-st47PosZSHrjECdjeIzZQbzivYBJFv6P2nv4cj2ypdI204DO+vZ7l5raGMiX4eXMJ53RfOIg+/s4DHVZ54Nu2A==", "path": "netstandard.library/2.0.3", "hashPath": "netstandard.library.2.0.3.nupkg.sha512" + }, + "Nullable/1.3.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-Mk4ZVDfAORTjvckQprCSehi1XgOAAlk5ez06Va/acRYEloN9t6d6zpzJRn5MEq7+RnagyFIq9r+kbWzLGd+6QA==", + "path": "nullable/1.3.1", + "hashPath": "nullable.1.3.1.nupkg.sha512" + }, + "StyleCop.Analyzers/1.2.0-beta.435": { + "type": "package", + "serviceable": true, + "sha512": "sha512-TADk7vdGXtfTnYCV7GyleaaRTQjfoSfZXprQrVMm7cSJtJbFc1QIbWPyLvrgrfGdfHbGmUPvaN4ODKNxg2jgPQ==", + "path": "stylecop.analyzers/1.2.0-beta.435", + "hashPath": "stylecop.analyzers.1.2.0-beta.435.nupkg.sha512" + }, + "StyleCop.Analyzers.Unstable/1.2.0.435": { + "type": "package", + "serviceable": true, + "sha512": "sha512-ouwPWZxbOV3SmCZxIRqHvljkSzkCyi1tDoMzQtDb/bRP8ctASV/iRJr+A2Gdj0QLaLmWnqTWDrH82/iP+X80Lg==", + "path": "stylecop.analyzers.unstable/1.2.0.435", + "hashPath": "stylecop.analyzers.unstable.1.2.0.435.nupkg.sha512" + }, + "BizHawk.BizInvoke/1.0.0.0": { + "type": "reference", + "serviceable": false, + "sha512": "" + }, + "BizHawk.Common/1.0.0.0": { + "type": "reference", + "serviceable": false, + "sha512": "" + }, + "System.Memory/4.0.1.1": { + "type": "reference", + "serviceable": false, + "sha512": "" + }, + "System.Numerics.Vectors/4.1.4.0": { + "type": "reference", + "serviceable": false, + "sha512": "" + }, + "System.Buffers/4.0.3.0": { + "type": "reference", + "serviceable": false, + "sha512": "" } } } \ No newline at end of file diff --git a/References/NLua.dll b/References/NLua.dll index ce3ea5a82b..be49c7bcb8 100644 Binary files a/References/NLua.dll and b/References/NLua.dll differ diff --git a/src/BizHawk.BizInvoke/BizInvoker.cs b/src/BizHawk.BizInvoke/BizInvoker.cs index 56ddf19bec..b7743c6af1 100644 --- a/src/BizHawk.BizInvoke/BizInvoker.cs +++ b/src/BizHawk.BizInvoke/BizInvoker.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Runtime.InteropServices; +using System.Security; using System.Text; using BizHawk.Common; using BizHawk.Common.CollectionExtensions; @@ -365,6 +366,9 @@ namespace BizHawk.BizInvoke returnType, paramTypes); + // live dangerously + method.SetCustomAttribute(new(typeof(SuppressUnmanagedCodeSecurityAttribute).GetConstructor(Type.EmptyTypes), Array.Empty())); + var il = method.GetILGenerator(); Label exc = default; diff --git a/src/BizHawk.Client.Common/BizHawk.Client.Common.csproj b/src/BizHawk.Client.Common/BizHawk.Client.Common.csproj index 47afd25531..33131ec100 100644 --- a/src/BizHawk.Client.Common/BizHawk.Client.Common.csproj +++ b/src/BizHawk.Client.Common/BizHawk.Client.Common.csproj @@ -9,8 +9,7 @@ - - + diff --git a/src/BizHawk.Client.Common/lua/NLuaTableHelper.cs b/src/BizHawk.Client.Common/lua/NLuaTableHelper.cs index f96a71c377..0ef2ddab79 100644 --- a/src/BizHawk.Client.Common/lua/NLuaTableHelper.cs +++ b/src/BizHawk.Client.Common/lua/NLuaTableHelper.cs @@ -22,19 +22,11 @@ namespace BizHawk.Client.Common _lua = lua; } - private LuaTable NewTable() - { - _lua.NewTable("__BIZHAWK_INTERNAL_TEMP_TABLE"); - var ret = _lua.GetTable("__BIZHAWK_INTERNAL_TEMP_TABLE"); - _lua["__BIZHAWK_INTERNAL_TEMP_TABLE"] = null; - return ret; - } - - public LuaTable CreateTable() => NewTable(); + public LuaTable CreateTable() => _lua.NewTable(); public LuaTable DictToTable(IReadOnlyDictionary dictionary) { - var table = NewTable(); + var table = CreateTable(); foreach (var (k, v) in dictionary) table[k] = v; return table; } @@ -46,7 +38,7 @@ namespace BizHawk.Client.Common public LuaTable ListToTable(IReadOnlyList list, int indexFrom = 1) { - var table = NewTable(); + var table = CreateTable(); for (int i = 0, l = list.Count; i != l; i++) table[indexFrom + i] = list[i]; return table; } @@ -63,7 +55,7 @@ namespace BizHawk.Client.Common public LuaTable ObjectToTable(object obj) { - var table = NewTable(); + var table = CreateTable(); foreach (var method in obj.GetType().GetMethods()) { if (!method.IsPublic) continue; diff --git a/src/BizHawk.Client.EmuHawk/tools/Lua/LuaLibraries.cs b/src/BizHawk.Client.EmuHawk/tools/Lua/LuaLibraries.cs index eaf2f6488d..6ac1344fae 100644 --- a/src/BizHawk.Client.EmuHawk/tools/Lua/LuaLibraries.cs +++ b/src/BizHawk.Client.EmuHawk/tools/Lua/LuaLibraries.cs @@ -128,7 +128,7 @@ namespace BizHawk.Client.EmuHawk private readonly MainForm _mainForm; - private Lua _lua = new() { State = { Encoding = Encoding.UTF8 } }; + private Lua _lua = new(); private LuaThread _currThread; private readonly NLuaTableHelper _th; @@ -141,9 +141,7 @@ namespace BizHawk.Client.EmuHawk private EmulationLuaLibrary EmulationLuaLibrary => (EmulationLuaLibrary)Libraries[typeof(EmulationLuaLibrary)]; - // nb: KeraLua isn't really the engine, NLua does the heavy lifting (KeraLua is just a thin layer for native lua) - // this is just done to differentiate against the old NLua engine, which was backed by KopiLua (c# impl of lua) instead of KeraLua - public string EngineName => "KeraLua"; + public string EngineName => "NLua+Lua"; public bool IsRebootingCore { get; set; } @@ -330,8 +328,8 @@ namespace BizHawk.Client.EmuHawk _currThread = null; var result = execResult switch { - KeraLua.LuaStatus.OK => (WaitForFrame: false, Terminated: true), - KeraLua.LuaStatus.Yield => (WaitForFrame: FrameAdvanceRequested, Terminated: false), + LuaStatus.OK => (WaitForFrame: false, Terminated: true), + LuaStatus.Yield => (WaitForFrame: FrameAdvanceRequested, Terminated: false), _ => throw new InvalidOperationException($"{nameof(_currThread.Resume)}() returned {execResult}?") }; diff --git a/src/BizHawk.Tests/BizHawk.Tests.csproj b/src/BizHawk.Tests/BizHawk.Tests.csproj index aa979053a5..e8cde53162 100644 --- a/src/BizHawk.Tests/BizHawk.Tests.csproj +++ b/src/BizHawk.Tests/BizHawk.Tests.csproj @@ -16,10 +16,9 @@ - - - - + + + diff --git a/src/BizHawk.Tests/Client.Common/lua/LuaTests.cs b/src/BizHawk.Tests/Client.Common/lua/LuaTests.cs index ae0c1d9cc7..4a4cea6678 100644 --- a/src/BizHawk.Tests/Client.Common/lua/LuaTests.cs +++ b/src/BizHawk.Tests/Client.Common/lua/LuaTests.cs @@ -2,11 +2,8 @@ using System.Collections.Generic; using System.Drawing; using System.Linq; -using System.Reflection; -using System.Text; using BizHawk.Client.Common; -using BizHawk.Common.StringExtensions; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -15,7 +12,7 @@ namespace BizHawk.Tests.Client.Common.Lua [TestClass] public class LuaTests { - private static readonly NLua.Lua LuaInstance = new() { State = { Encoding = Encoding.UTF8 } }; + private static readonly NLua.Lua LuaInstance = new(); private static readonly NLuaTableHelper _th = new(LuaInstance, Console.WriteLine); private static object? ExpectedValue { get; set; }