using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Lua.Runtime; namespace Lua; public enum LuaValueType : byte { Nil, Boolean, String, Number, Function, UserData, Table, } [StructLayout(LayoutKind.Auto)] public readonly struct LuaValue : IEquatable { public static readonly LuaValue Nil = default; readonly LuaValueType type; readonly double value; readonly object? referenceValue; public LuaValueType Type => type; public bool TryRead(out T result) { var t = typeof(T); switch (type) { case LuaValueType.Number: if (t == typeof(int)) { var v = (int)value; result = Unsafe.As(ref v); return true; } else if (t == typeof(long)) { var v = (long)value; result = Unsafe.As(ref v); return true; } else if (t == typeof(float)) { var v = (float)value; result = Unsafe.As(ref v); return true; } else if (t == typeof(double)) { var v = value; result = Unsafe.As(ref v); return true; } else if (t == typeof(object)) { result = (T)(object)value; return true; } else { break; } case LuaValueType.Boolean: if (t == typeof(bool)) { var v = value == 1; result = Unsafe.As(ref v); return true; } else if (t == typeof(object)) { result = (T)(object)value; return true; } else { break; } case LuaValueType.String: if (t == typeof(string)) { var v = (string)referenceValue!; result = Unsafe.As(ref v); return true; } else if (t == typeof(object)) { result = (T)referenceValue!; return true; } else { break; } case LuaValueType.Function: if (t == typeof(LuaFunction) || t.IsSubclassOf(typeof(LuaFunction))) { var v = (LuaFunction)referenceValue!; result = Unsafe.As(ref v); return true; } else if (t == typeof(object)) { result = (T)referenceValue!; return true; } else { break; } case LuaValueType.UserData: if (referenceValue is T userData) { result = userData; return true; } break; case LuaValueType.Table: if (t == typeof(LuaTable)) { var v = (LuaTable)referenceValue!; result = Unsafe.As(ref v); return true; } else if (t == typeof(object)) { result = (T)referenceValue!; return true; } else { break; } } result = default!; return false; } public T Read() { if (!TryRead(out var result)) throw new InvalidOperationException($"Cannot convert LuaValueType.{Type} to {typeof(T).FullName}."); return result; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool ToBoolean() { if (Type is LuaValueType.Nil) return false; if (TryRead(out var result)) return result; return true; } public LuaValue(bool value) { type = LuaValueType.Boolean; this.value = value ? 1 : 0; } public LuaValue(double value) { type = LuaValueType.Number; this.value = value; } public LuaValue(string value) { type = LuaValueType.String; referenceValue = value; } public LuaValue(LuaFunction value) { type = LuaValueType.Function; referenceValue = value; } public LuaValue(LuaTable value) { type = LuaValueType.Table; referenceValue = value; } public LuaValue(object? value) { type = LuaValueType.UserData; referenceValue = value; } public static implicit operator LuaValue(bool value) { return new(value); } public static implicit operator LuaValue(double value) { return new(value); } public static implicit operator LuaValue(string value) { return new(value); } public static implicit operator LuaValue(LuaTable value) { return new(value); } public static implicit operator LuaValue(LuaFunction value) { return new(value); } public override int GetHashCode() { var valueHash = type switch { LuaValueType.Nil => 0, LuaValueType.Boolean => Read().GetHashCode(), LuaValueType.String => Read().GetHashCode(), LuaValueType.Number => Read().GetHashCode(), LuaValueType.Function => Read().GetHashCode(), LuaValueType.Table => Read().GetHashCode(), LuaValueType.UserData => referenceValue == null ? 0 : referenceValue.GetHashCode(), _ => 0, }; return HashCode.Combine(type, valueHash); } public bool Equals(LuaValue other) { if (other.Type != Type) return false; return type switch { LuaValueType.Nil => true, LuaValueType.Boolean => Read().Equals(other.Read()), LuaValueType.String => Read().Equals(other.Read()), LuaValueType.Number => Read().Equals(other.Read()), LuaValueType.Function => Read().Equals(other.Read()), LuaValueType.Table => Read().Equals(other.Read()), LuaValueType.UserData => referenceValue == other.referenceValue, _ => false, }; } public override bool Equals(object? obj) { return obj is LuaValue value1 && Equals(value1); } public static bool operator ==(LuaValue a, LuaValue b) { return a.Equals(b); } public static bool operator !=(LuaValue a, LuaValue b) { return !a.Equals(b); } public override string? ToString() { return type switch { LuaValueType.Nil => "Nil", LuaValueType.Boolean => Read().ToString(), LuaValueType.String => Read().ToString(), LuaValueType.Number => Read().ToString(), LuaValueType.Function => Read().ToString(), LuaValueType.Table => Read().ToString(), LuaValueType.UserData => referenceValue?.ToString(), _ => "", }; } public static bool TryGetLuaValueType(Type type, out LuaValueType result) { if (type == typeof(double) || type == typeof(float) || type == typeof(int) || type == typeof(long)) { result = LuaValueType.Number; return true; } else if (type == typeof(bool)) { result = LuaValueType.Boolean; return true; } else if (type == typeof(string)) { result = LuaValueType.String; return true; } else if (type == typeof(LuaFunction) || type.IsSubclassOf(typeof(LuaFunction))) { result = LuaValueType.Function; return true; } else if (type == typeof(LuaTable)) { result = LuaValueType.Table; return true; } result = default; return false; } internal async ValueTask CallToStringAsync(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) { if (this.TryGetMetamethod(Metamethods.ToString, out var metamethod)) { if (!metamethod.TryRead(out var func)) { LuaRuntimeException.AttemptInvalidOperation(context.State.GetTracebacks(), "call", metamethod); } context.State.Push(this); return await func.InvokeAsync(context with { ArgumentCount = 1, }, buffer, cancellationToken); } else { buffer.Span[0] = ToString()!; return 1; } } }