using System.Globalization; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Lua.Internal; using Lua.Runtime; namespace Lua; public enum LuaValueType : byte { Nil, Boolean, String, Number, Function, Thread, LightUserData, UserData, Table } [StructLayout(LayoutKind.Auto)] public readonly struct LuaValue : IEquatable { public static readonly LuaValue Nil = default; public readonly LuaValueType Type; readonly double value; readonly object? referenceValue; internal LuaValue(LuaValueType type, double value, object? referenceValue) { Type = type; this.value = value; this.referenceValue = referenceValue; } public bool TryRead(out T result) { var t = typeof(T); switch (Type) { case LuaValueType.Number: 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(int)) { if (!MathEx.IsInteger(value)) { break; } var v = (int)value; result = Unsafe.As(ref v); return true; } else if (t == typeof(long)) { if (!MathEx.IsInteger(value)) { break; } var v = (long)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 != 0; 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 = referenceValue!; result = Unsafe.As(ref v); return true; } else if (t == typeof(double)) { result = default!; return TryParseToDouble(out Unsafe.As(ref result)); } 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 = referenceValue!; result = Unsafe.As(ref v); return true; } else if (t == typeof(object)) { result = (T)referenceValue!; return true; } else { break; } case LuaValueType.Thread: if (t == typeof(LuaState)) { var v = referenceValue!; result = Unsafe.As(ref v); return true; } else if (t == typeof(object)) { result = (T)referenceValue!; return true; } else { break; } case LuaValueType.LightUserData: { if (referenceValue is T tValue) { result = tValue; return true; } break; } case LuaValueType.UserData: if (t == typeof(ILuaUserData) || typeof(ILuaUserData).IsAssignableFrom(t)) { if (referenceValue is T tValue) { result = tValue; return true; } break; } else if (t == typeof(object)) { result = (T)referenceValue!; return true; } else { break; } case LuaValueType.Table: if (t == typeof(LuaTable)) { var v = referenceValue!; result = Unsafe.As(ref v); return true; } else if (t == typeof(object)) { result = (T)referenceValue!; return true; } else { break; } } result = default!; return false; } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal bool TryReadBool(out bool result) { if (Type == LuaValueType.Boolean) { result = value != 0; return true; } result = default!; return false; } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal bool TryReadNumber(out double result) { if (Type == LuaValueType.Number) { result = value; return true; } result = default!; return false; } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal bool TryReadTable(out LuaTable result) { if (Type == LuaValueType.Table) { var v = referenceValue!; result = Unsafe.As(ref v); return true; } result = default!; return false; } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal bool TryReadFunction(out LuaFunction result) { if (Type == LuaValueType.Function) { var v = referenceValue!; result = Unsafe.As(ref v); return true; } result = default!; return false; } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal bool TryReadString(out string result) { if (Type == LuaValueType.String) { var v = referenceValue!; result = Unsafe.As(ref v); return true; } result = default!; return false; } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal bool TryReadDouble(out double result) { if (Type == LuaValueType.Number) { result = value; return true; } return TryParseToDouble(out result); } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static bool TryReadOrSetDouble(ref LuaValue luaValue, out double result) { if (luaValue.Type == LuaValueType.Number) { result = luaValue.value; return true; } if (luaValue.TryParseToDouble(out result)) { luaValue = result; return true; } return false; } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal double UnsafeReadDouble() { return value; } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal string UnsafeReadString() { return Unsafe.As(referenceValue!); } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal object UnsafeReadObject() { return Unsafe.As(referenceValue!); } bool TryParseToDouble(out double result) { if (Type != LuaValueType.String) { result = default!; return false; } var str = Unsafe.As(referenceValue!); var span = str.AsSpan().Trim(); if (span.Length == 0) { result = default!; return false; } var sign = 1; var first = span[0]; if (first is '+') { sign = 1; span = span[1..]; } else if (first is '-') { sign = -1; span = span[1..]; } if (span.Length > 2 && span[0] is '0' && span[1] is 'x' or 'X') { // TODO: optimize try { var d = HexConverter.ToDouble(span) * sign; result = d; return true; } catch (FormatException) { result = default!; return false; } } else { return double.TryParse(str, NumberStyles.Float, CultureInfo.InvariantCulture, out result); } } public T Read() { if (!TryRead(out var result)) { throw new InvalidOperationException($"Cannot convert LuaValueType.{Type} to {typeof(T).FullName}."); } return result; } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal T UnsafeRead() { switch (Type) { case LuaValueType.Boolean: { var v = value != 0; return Unsafe.As(ref v); } case LuaValueType.Number: { var v = value; return Unsafe.As(ref v); } case LuaValueType.String: case LuaValueType.Thread: case LuaValueType.Function: case LuaValueType.Table: case LuaValueType.LightUserData: case LuaValueType.UserData: { var v = referenceValue!; return Unsafe.As(ref v); } } return default!; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool ToBoolean() { if (Type == LuaValueType.Boolean) { return value != 0; } if (Type is LuaValueType.Nil) { return false; } return true; } public static LuaValue FromObject(object obj) { return obj switch { null => Nil, LuaValue luaValue => luaValue, bool boolValue => boolValue, double doubleValue => doubleValue, string stringValue => stringValue, LuaFunction luaFunction => luaFunction, LuaTable luaTable => luaTable, LuaState luaThread => luaThread, ILuaUserData userData => FromUserData(userData), int intValue => intValue, long longValue => longValue, float floatValue => floatValue, _ => new(obj) }; } public static LuaValue FromUserData(ILuaUserData? userData) { if (userData is null) { return Nil; } return new(userData); } [MethodImpl(MethodImplOptions.AggressiveInlining)] LuaValue(object obj) { Type = LuaValueType.LightUserData; referenceValue = obj; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public LuaValue(bool value) { Type = LuaValueType.Boolean; this.value = value ? 1 : 0; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public LuaValue(double value) { Type = LuaValueType.Number; this.value = value; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public LuaValue(string value) { Type = LuaValueType.String; referenceValue = value; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public LuaValue(LuaFunction value) { Type = LuaValueType.Function; referenceValue = value; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public LuaValue(LuaTable value) { Type = LuaValueType.Table; referenceValue = value; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public LuaValue(LuaState value) { Type = LuaValueType.Thread; referenceValue = value; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public LuaValue(ILuaUserData value) { Type = LuaValueType.UserData; referenceValue = value; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static implicit operator LuaValue(bool value) { return new(value); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static implicit operator LuaValue(double value) { return new(value); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static implicit operator LuaValue(string value) { return new(value); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static implicit operator LuaValue(LuaTable value) { return new(value); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static implicit operator LuaValue(LuaFunction value) { return new(value); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static implicit operator LuaValue(LuaState value) { return new(value); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int GetHashCode() { return Type switch { LuaValueType.Nil => 0, LuaValueType.Boolean or LuaValueType.Number => value.GetHashCode(), LuaValueType.String => Unsafe.As(referenceValue)!.GetHashCode(), _ => referenceValue!.GetHashCode() }; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(LuaValue other) { if (other.Type != Type) { return false; } return Type switch { LuaValueType.Nil => true, LuaValueType.Boolean or LuaValueType.Number => other.value == value, LuaValueType.String => Unsafe.As(other.referenceValue) == Unsafe.As(referenceValue), _ => other.referenceValue == referenceValue }; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool EqualsForDict(LuaValue other) { return other.Type == Type && Type switch { LuaValueType.Boolean or LuaValueType.Number => other.value == value, LuaValueType.String => Unsafe.As(other.referenceValue) == Unsafe.As(referenceValue), _ => other.referenceValue == referenceValue }; } public override bool Equals(object? obj) { return obj is LuaValue value1 && Equals(value1); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(LuaValue a, LuaValue b) { return a.Equals(b); } [MethodImpl(MethodImplOptions.AggressiveInlining)] 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() ? "true" : "false", LuaValueType.String => Read(), LuaValueType.Number => Read().ToString(CultureInfo.InvariantCulture), LuaValueType.Function => $"function: {referenceValue!.GetHashCode()}", LuaValueType.Thread => $"thread: {referenceValue!.GetHashCode()}", LuaValueType.Table => $"table: {referenceValue!.GetHashCode()}", LuaValueType.LightUserData => $"userdata: {referenceValue!.GetHashCode()}", LuaValueType.UserData => $"userdata: {referenceValue!.GetHashCode()}", _ => "" }; } public string TypeToString() { return ToString(Type); } public static string ToString(LuaValueType type) { return type switch { LuaValueType.Nil => "nil", LuaValueType.Boolean => "boolean", LuaValueType.String => "string", LuaValueType.Number => "number", LuaValueType.Function => "function", LuaValueType.Thread => "thread", LuaValueType.Table => "table", LuaValueType.LightUserData => "light userdata", LuaValueType.UserData => "userdata", _ => "" }; } 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; } else if (type == typeof(LuaState)) { result = LuaValueType.Thread; return true; } else if (type == typeof(ILuaUserData) || type.IsAssignableFrom(typeof(ILuaUserData))) { result = LuaValueType.UserData; return true; } result = default; return false; } internal ValueTask CallToStringAsync(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { if (this.TryGetMetamethod(context.GlobalState, Metamethods.ToString, out var metamethod)) { var stack = context.State.Stack; stack.Push(metamethod); stack.Push(this); return LuaVirtualMachine.Call(context.State, stack.Count - 2, stack.Count - 2, cancellationToken); } else { context.State.Stack.Push(ToString()); return default; } } }