qamp/Qrakhen.Qamp.Core/Values/Value.cs

124 lines
4.0 KiB
C#

using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using Qrakhen.Qamp.Core.Values.Objects;
using String = Qrakhen.Qamp.Core.Values.Objects.String;
using T = Qrakhen.Qamp.Core.Values.ValueType;
namespace Qrakhen.Qamp.Core.Values;
public interface IValue
{
ValueType ValueType { get; }
}
[StructLayout(LayoutKind.Explicit, Size = 0x10)]
public readonly struct Value : IValue, IDebug<string>
{
public static readonly Value Void = new Value();
[FieldOffset(0x00)] [Serialized] public readonly ulong Unsigned;
[FieldOffset(0x00)] [Serialized] public readonly long Signed;
[FieldOffset(0x00)] [Serialized] public readonly bool Bool;
[FieldOffset(0x00)] [Serialized] public readonly char Char;
[FieldOffset(0x00)] [Serialized] public readonly double Decimal;
[FieldOffset(0x00)] [Serialized] public readonly Address Address;
[FieldOffset(0x00)] [Serialized] public readonly Ptr Ptr;
[FieldOffset(0x00)] [Serialized] public readonly Ref Ref;
[FieldOffset(0x08)] [Serialized] public readonly T Type;
[FieldOffset(0x0c)] [Serialized] public readonly uint Meta; // like lengths of arrays or lists
public T ValueType => Type;
private Value(T type) => Type = type;
public Value() : this(T.Void) { }
public Value(ulong unsigned) : this(T.Unsigned) => Unsigned = unsigned;
public Value(long signed) : this(T.Signed) => Signed = signed;
public Value(char character) : this(T.Char) => Char = character;
public Value(bool boolean) : this(T.Bool) => Bool = boolean;
public Value(double @decimal) : this(T.Decimal) => Decimal = @decimal;
public Value(Address address) : this(T.Address) => Address = address;
public Value(Ptr ptr) : this(ptr.Value?.Type ?? T.Pointer) => Ptr = ptr;
public Value(Ref @ref) : this(T.Reference) => Ref = @ref;
public dynamic? Dynamic => AsDynamic();
public bool Is(T type, bool exact = true)
{
return exact ? (Type & type) == type : (Type & type) > 0;
}
private unsafe dynamic? AsDynamic(bool throwWhenNull = true)
{
if (Type.HasFlag(T.Object))
return Ptr.Value;
return Type switch {
T.Void => throwWhenNull ? throw new NullReferenceException() : null,
T.Unsigned => Unsigned,
T.Signed => Signed,
T.Decimal => Decimal,
T.Bool => Bool,
T.Address => Address,
T.Reference => (Value)(*Ref.Value),
_ => throwWhenNull ? throw new NullReferenceException() : null,
};
}
public static Value FromAny(object? any = null)
{
if (any == null)
return new Value();
Type type = any.GetType();
return new Value();
}
public override bool Equals([NotNullWhen(true)] object? obj)
{
return obj switch {
Value value when Type == value.Type => Unsigned == value.Unsigned,
Value value => Dynamic?.Equals(value.Dynamic),
_ => false
};
}
public override int GetHashCode() => Unsigned.GetHashCode();
public override unsafe string ToString()
{
string type = Type.ToString();
if (Type.HasFlag(T.Pointer))
type = Ptr.Value?.Type.ToString() ?? "NullPtr";
return $"[{type}, {AsDynamic(false) ?? "null"}]";
}
public string Debug(DebugLevel level = DebugLevel.None)
{
if (level < DebugLevel.Strong)
return Debugger.GetContextString(this);
string str = $"{Debugger.GetContextString(this)}\n";
string[] lines =
[
$"{nameof(Meta)}: {Meta}",
$"{nameof(Unsigned)}: {Unsigned}",
$"{nameof(Signed)}: {Signed}",
$"{nameof(Bool)}: {Bool}",
$"{nameof(Char)}: {Char}",
$"{nameof(Decimal)}: {Decimal}",
$"{nameof(Address)}: {Address}",
$"{nameof(Ref)}: {Ref}",
$"{nameof(Ptr)}: {Ptr}"
];
return str + string.Join("\n - ", lines);
}
}
public class SerializedAttribute : Attribute;