using Microsoft.Xna.Framework; using Sigil; using Sigil.NonGeneric; using System; using System.Collections; using System.Collections.Generic; using System.Linq.Expressions; using System.Reflection; namespace Barotrauma.LuaCs; internal static class SigilExtensions { /// /// Puts a type on the stack, as a object instead of a /// runtime type token. /// /// The IL emitter. /// The type to put on the stack. public static void LoadType(this Emit il, Type type) { if (type == null) throw new ArgumentNullException(nameof(type)); il.LoadConstant(type); // ldtoken // This converts the type token into a Type object il.Call(typeof(Type).GetMethod( name: nameof(Type.GetTypeFromHandle), bindingAttr: BindingFlags.Public | BindingFlags.Static, binder: null, types: new Type[] { typeof(RuntimeTypeHandle) }, modifiers: null)); } /// /// Converts the value on the stack to . /// /// The IL emitter. /// The type of the value on the stack. public static void ToObject(this Emit il, Type type) { if (type == null) throw new ArgumentNullException(nameof(type)); il.DerefIfByRef(ref type); if (type.IsValueType) { il.Box(type); } else if (type != typeof(object)) { il.CastClass(); } } /// /// Deferences the value on stack if the provided type is ByRef. /// /// The IL emitter. /// The type to check if ByRef. public static void DerefIfByRef(this Emit il, Type type) => il.DerefIfByRef(ref type); /// /// Deferences the value on stack if the provided type is ByRef. /// /// The IL emitter. /// The type to check if ByRef. public static void DerefIfByRef(this Emit il, ref Type type) { if (type == null) throw new ArgumentNullException(nameof(type)); if (type.IsByRef) { type = type.GetElementType(); if (type.IsValueType) { il.LoadObject(type); } else { il.LoadIndirect(type); } } } // Copied from https://github.com/evilfactory/moonsharp/blob/5264656c6442e783f3c75082cce69a93d66d4cc0/src/MoonSharp.Interpreter/Interop/Converters/ScriptToClrConversions.cs#L79-L99 private static MethodInfo GetImplicitOperatorMethod(Type baseType, Type targetType) { try { return Expression.Convert(Expression.Parameter(baseType, null), targetType).Method; } catch { if (baseType.BaseType != null) { return GetImplicitOperatorMethod(baseType.BaseType, targetType); } if (targetType.BaseType != null) { return GetImplicitOperatorMethod(baseType, targetType.BaseType); } return null; } } /// /// Loads a local variable and casts it to the target type. /// /// The IL emitter. /// The value to cast. Must be of type . /// The type to cast into. public static void LoadLocalAndCast(this Emit il, Local value, Type targetType) { if (value == null) throw new ArgumentNullException(nameof(value)); if (targetType == null) throw new ArgumentNullException(nameof(targetType)); if (value.LocalType != typeof(object)) { throw new ArgumentException($"Expected local type {typeof(object)}; got {value.LocalType}.", nameof(value)); } var guid = Guid.NewGuid().ToString("N"); if (targetType.IsByRef) { targetType = targetType.GetElementType(); } // IL: var baseType = value.GetType(); var baseType = il.DeclareLocal(typeof(Type), $"cast_baseType_{guid}"); il.LoadLocal(value); il.Call(typeof(object).GetMethod("GetType")); il.StoreLocal(baseType); // IL: var implicitOperatorMethod = SigilExtensions.GetImplicitOperatorMethod(baseType, ); var implicitOperatorMethod = il.DeclareLocal(typeof(MethodInfo), $"cast_implicitOperatorMethod_{guid}"); il.LoadLocal(baseType); il.LoadType(targetType); il.Call(typeof(SigilExtensions).GetMethod(nameof(GetImplicitOperatorMethod), BindingFlags.NonPublic | BindingFlags.Static)); il.StoreLocal(implicitOperatorMethod); // IL: castValue; var castValue = il.DeclareLocal(targetType, $"cast_castValue_{guid}"); // IL: if (implicitConversionMethod != null) il.LoadLocal(implicitOperatorMethod); il.Branch((il) => { // IL: var methodInvokeParams = new object[1]; var methodInvokeParams = il.DeclareLocal(typeof(object[]), $"cast_methodInvokeParams_{guid}"); il.LoadConstant(1); il.NewArray(typeof(object)); il.StoreLocal(methodInvokeParams); // IL: methodInvokeParams[0] = value; il.LoadLocal(methodInvokeParams); il.LoadConstant(0); il.LoadLocal(value); il.StoreElement(); // IL: castValue = ()implicitConversionMethod.Invoke(null, methodInvokeParams); il.LoadLocal(implicitOperatorMethod); il.LoadNull(); // first parameter is null because implicit cast operators are static il.LoadLocal(methodInvokeParams); il.Call(typeof(MethodInfo).GetMethod("Invoke", new[] { typeof(object), typeof(object[]) })); if (targetType.IsValueType) { il.UnboxAny(targetType); } else { il.CastClass(targetType); } il.StoreLocal(castValue); }, (il) => { // IL: castValue = ()value; il.LoadLocal(value); if (targetType.IsValueType) { il.UnboxAny(targetType); } else { il.CastClass(targetType); } il.StoreLocal(castValue); }); il.LoadLocal(castValue); } /// /// Emits a call to . /// /// The IL emitter. /// The string format. /// The local variables passed to string.Format. public static void FormatString(this Emit il, string format, params Local[] args) { if (format == null) throw new ArgumentNullException(nameof(format)); if (args == null) throw new ArgumentNullException(nameof(args)); var guid = Guid.NewGuid().ToString("N"); var listType = typeof(List<>).MakeGenericType(typeof(object)); var list = il.DeclareLocal(listType, $"formatString_list_{guid}"); il.NewObject(listType); il.StoreLocal(list); foreach (var arg in args) { il.LoadLocal(list); il.LoadLocal(arg); il.ToObject(arg.LocalType); il.CallVirtual(listType.GetMethod("Add", new[] { typeof(object) })); } var arr = il.DeclareLocal($"formatString_arr_{guid}"); il.LoadLocal(list); il.CallVirtual(listType.GetMethod("ToArray", new Type[0])); il.StoreLocal(arr); il.LoadConstant(format); il.LoadLocal(arr); il.Call(typeof(string).GetMethod("Format", new[] { typeof(string), typeof(object[]) })); } /// /// Emits a call to . /// /// The IL emitter. /// The message to print. public static void NewMessage(this Emit il, string message) { var newMessage = typeof(DebugConsole).GetMethod( name: nameof(DebugConsole.NewMessage), bindingAttr: BindingFlags.Public | BindingFlags.Static, binder: null, types: new Type[] { typeof(string), typeof(Color?), typeof(bool) }, modifiers: null); il.LoadConstant(message); il.Call(typeof(Color).GetProperty(nameof(Color.LightBlue), BindingFlags.Public | BindingFlags.Static).GetGetMethod()); il.LoadConstant(false); il.Call(newMessage); } /// /// Emits a call to , /// using the string on the stack. /// /// The IL emitter. public static void NewMessage(this Emit il) { var newMessage = typeof(DebugConsole).GetMethod( name: nameof(DebugConsole.NewMessage), bindingAttr: BindingFlags.Public | BindingFlags.Static, binder: null, types: new Type[] { typeof(string), typeof(Color?), typeof(bool) }, modifiers: null); il.Call(typeof(Color).GetProperty(nameof(Color.LightBlue), BindingFlags.Public | BindingFlags.Static).GetGetMethod()); il.LoadConstant(false); il.Call(newMessage); } /// /// Emits a foreach loop that iterates over an local variable. /// /// The type of elements in the enumerable. /// The IL emitter. /// The enumerable. /// The body of code to run on each iteration. public static void ForEachEnumerable(this Emit il, Local enumerable, Action action) { if (enumerable == null) throw new ArgumentNullException(nameof(enumerable)); if (action == null) throw new ArgumentNullException(nameof(action)); if (!typeof(IEnumerable).IsAssignableFrom(enumerable.LocalType)) { throw new ArgumentException($"Expected local type {typeof(IEnumerator)}; got {enumerable.LocalType}.", nameof(enumerable)); } var guid = Guid.NewGuid().ToString("N"); var enumerator = il.DeclareLocal>($"forEachEnumerable_enumerator_{guid}"); il.LoadLocal(enumerable); il.CallVirtual(typeof(IEnumerable).GetMethod("GetEnumerator")); il.StoreLocal(enumerator); ForEachEnumerator(il, enumerator, action); } /// /// Emits a foreach loop that iterates over an local variable. /// /// The type of elements in the enumerable. /// The IL emitter. /// The enumerator. /// The body of code to run on each iteration. public static void ForEachEnumerator(this Emit il, Local enumerator, Action action) { if (enumerator == null) throw new ArgumentNullException(nameof(enumerator)); if (action == null) throw new ArgumentNullException(nameof(action)); if (!typeof(IEnumerator).IsAssignableFrom(enumerator.LocalType)) { throw new ArgumentException($"Expected local type {typeof(IEnumerator)}; got {enumerator.LocalType}.", nameof(enumerator)); } var guid = Guid.NewGuid().ToString("N"); var labelLoopStart = il.DefineLabel($"forEach_loopStart_{guid}"); var labelMoveNext = il.DefineLabel($"forEach_moveNext_{guid}"); var labelLeave = il.DefineLabel($"forEach_leave_{guid}"); il.BeginExceptionBlock(out var exceptionBlock); il.Branch(labelMoveNext); // MoveNext() needs to be called at least once before iterating il.MarkLabel(labelLoopStart); // IL: var current = enumerator.Current; var current = il.DeclareLocal($"forEachEnumerator_current_{guid}"); il.LoadLocal(enumerator); il.CallVirtual(enumerator.LocalType.GetProperty("Current").GetGetMethod()); il.StoreLocal(current); action(il, current, labelLeave); il.MarkLabel(labelMoveNext); il.LoadLocal(enumerator); il.CallVirtual(typeof(IEnumerator).GetMethod("MoveNext")); il.BranchIfTrue(labelLoopStart); // loop if MoveNext() returns true // IL: finally { enumerator.Dispose(); } il.BeginFinallyBlock(exceptionBlock, out var finallyBlock); il.LoadLocal(enumerator); il.CallVirtual(typeof(IDisposable).GetMethod("Dispose")); il.EndFinallyBlock(finallyBlock); il.EndExceptionBlock(exceptionBlock); il.MarkLabel(labelLeave); } /// /// Emits a branch that only executes if the last value on the stack /// is truthy (e.g. non-null references, 1, etc). /// /// The IL emitter. /// The body of code to run if the value is truthy. public static void If(this Emit il, Action action) { if (action == null) throw new ArgumentNullException(nameof(action)); il.Branch(@if: action); } /// /// Emits a branch that only executes if the last value on the stack /// is falsy (e.g. null references, 0, etc). /// /// The IL emitter. /// The body of code to run if the value is falsy. public static void IfNot(this Emit il, Action action) { if (action == null) throw new ArgumentNullException(nameof(action)); il.Branch(@else: action); } /// /// Emits two branches that diverge based on a condition -- analogous /// to an if-else statement. If either /// or are omitted, it behaves the same as /// /// and . /// /// The IL emitter. /// The body of code to run if the value is truthy. /// The body of code to run if the value is falsy. public static void Branch(this Emit il, Action @if = null, Action @else = null) { if (@if == null && @else == null) throw new ArgumentException("At least one of the two branches must be defined."); var guid = Guid.NewGuid().ToString("N"); var labelEnd = il.DefineLabel($"branch_end_{guid}"); if (@if != null && @else != null) { var labelElse = il.DefineLabel($"branch_else_{guid}"); il.BranchIfFalse(labelElse); @if(il); il.Branch(labelEnd); il.MarkLabel(labelElse); @else(il); } else if (@if != null) { il.BranchIfFalse(labelEnd); @if(il); } else { il.BranchIfTrue(labelEnd); @else(il); } il.MarkLabel(labelEnd); } }