268 lines
7.2 KiB
C#
268 lines
7.2 KiB
C#
using System;
|
|||
|
|
using ProtoBuf.Compiler;
|
||
|
|
using ProtoBuf.Meta;
|
||
|
|
|
||
|
|
namespace ProtoBuf.Serializers;
|
||
|
|
|
||
|
|
internal sealed class EnumSerializer : IProtoSerializer
|
||
|
|
{
|
||
|
|
public readonly struct EnumPair(int wireValue, object raw, Type type)
|
||
|
|
{
|
||
|
|
public readonly object RawValue = raw;
|
||
|
|
|
||
|
|
public readonly Enum TypedValue = (Enum)Enum.ToObject(type, raw);
|
||
|
|
|
||
|
|
public readonly int WireValue = wireValue;
|
||
|
|
}
|
||
|
|
|
||
|
|
private readonly Type enumType;
|
||
|
|
|
||
|
|
private readonly EnumPair[] map;
|
||
|
|
|
||
|
|
public Type ExpectedType => enumType;
|
||
|
|
|
||
|
|
bool IProtoSerializer.RequiresOldValue => false;
|
||
|
|
|
||
|
|
bool IProtoSerializer.ReturnsValue => true;
|
||
|
|
|
||
|
|
public EnumSerializer(Type enumType, EnumPair[] map)
|
||
|
|
{
|
||
|
|
this.enumType = enumType ?? throw new ArgumentNullException("enumType");
|
||
|
|
this.map = map;
|
||
|
|
if (map == null)
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
for (int i = 1; i < map.Length; i++)
|
||
|
|
{
|
||
|
|
for (int j = 0; j < i; j++)
|
||
|
|
{
|
||
|
|
if (map[i].WireValue == map[j].WireValue && !object.Equals(map[i].RawValue, map[j].RawValue))
|
||
|
|
{
|
||
|
|
int wireValue = map[i].WireValue;
|
||
|
|
throw new ProtoException("Multiple enums with wire-value " + wireValue);
|
||
|
|
}
|
||
|
|
if (object.Equals(map[i].RawValue, map[j].RawValue) && map[i].WireValue != map[j].WireValue)
|
||
|
|
{
|
||
|
|
throw new ProtoException("Multiple enums with deserialized-value " + map[i].RawValue);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private ProtoTypeCode GetTypeCode()
|
||
|
|
{
|
||
|
|
Type underlyingType = Helpers.GetUnderlyingType(enumType);
|
||
|
|
if ((object)underlyingType == null)
|
||
|
|
{
|
||
|
|
underlyingType = enumType;
|
||
|
|
}
|
||
|
|
return Helpers.GetTypeCode(underlyingType);
|
||
|
|
}
|
||
|
|
|
||
|
|
private int EnumToWire(object value)
|
||
|
|
{
|
||
|
|
return GetTypeCode() switch
|
||
|
|
{
|
||
|
|
ProtoTypeCode.Byte => (byte)value,
|
||
|
|
ProtoTypeCode.SByte => (sbyte)value,
|
||
|
|
ProtoTypeCode.Int16 => (short)value,
|
||
|
|
ProtoTypeCode.Int32 => (int)value,
|
||
|
|
ProtoTypeCode.Int64 => (int)(long)value,
|
||
|
|
ProtoTypeCode.UInt16 => (ushort)value,
|
||
|
|
ProtoTypeCode.UInt32 => (int)(uint)value,
|
||
|
|
ProtoTypeCode.UInt64 => (int)(ulong)value,
|
||
|
|
_ => throw new InvalidOperationException(),
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
private object WireToEnum(int value)
|
||
|
|
{
|
||
|
|
return GetTypeCode() switch
|
||
|
|
{
|
||
|
|
ProtoTypeCode.Byte => Enum.ToObject(enumType, (byte)value),
|
||
|
|
ProtoTypeCode.SByte => Enum.ToObject(enumType, (sbyte)value),
|
||
|
|
ProtoTypeCode.Int16 => Enum.ToObject(enumType, (short)value),
|
||
|
|
ProtoTypeCode.Int32 => Enum.ToObject(enumType, value),
|
||
|
|
ProtoTypeCode.Int64 => Enum.ToObject(enumType, (long)value),
|
||
|
|
ProtoTypeCode.UInt16 => Enum.ToObject(enumType, (ushort)value),
|
||
|
|
ProtoTypeCode.UInt32 => Enum.ToObject(enumType, (uint)value),
|
||
|
|
ProtoTypeCode.UInt64 => Enum.ToObject(enumType, (ulong)value),
|
||
|
|
_ => throw new InvalidOperationException(),
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
public object Read(object value, ProtoReader source)
|
||
|
|
{
|
||
|
|
int num = source.ReadInt32();
|
||
|
|
if (map == null)
|
||
|
|
{
|
||
|
|
return WireToEnum(num);
|
||
|
|
}
|
||
|
|
for (int i = 0; i < map.Length; i++)
|
||
|
|
{
|
||
|
|
if (map[i].WireValue == num)
|
||
|
|
{
|
||
|
|
return map[i].TypedValue;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
source.ThrowEnumException(ExpectedType, num);
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
public void Write(object value, ProtoWriter dest)
|
||
|
|
{
|
||
|
|
if (map == null)
|
||
|
|
{
|
||
|
|
ProtoWriter.WriteInt32(EnumToWire(value), dest);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
for (int i = 0; i < map.Length; i++)
|
||
|
|
{
|
||
|
|
if (object.Equals(map[i].TypedValue, value))
|
||
|
|
{
|
||
|
|
ProtoWriter.WriteInt32(map[i].WireValue, dest);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
ProtoWriter.ThrowEnumException(dest, value);
|
||
|
|
}
|
||
|
|
|
||
|
|
void IProtoSerializer.EmitWrite(CompilerContext ctx, Local valueFrom)
|
||
|
|
{
|
||
|
|
ProtoTypeCode typeCode = GetTypeCode();
|
||
|
|
if (map == null)
|
||
|
|
{
|
||
|
|
ctx.LoadValue(valueFrom);
|
||
|
|
ctx.ConvertToInt32(typeCode, uint32Overflow: false);
|
||
|
|
ctx.EmitBasicWrite("WriteInt32", null);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
using Local local = ctx.GetLocalWithValue(ExpectedType, valueFrom);
|
||
|
|
CodeLabel label = ctx.DefineLabel();
|
||
|
|
for (int i = 0; i < map.Length; i++)
|
||
|
|
{
|
||
|
|
CodeLabel label2 = ctx.DefineLabel();
|
||
|
|
CodeLabel label3 = ctx.DefineLabel();
|
||
|
|
ctx.LoadValue(local);
|
||
|
|
WriteEnumValue(ctx, typeCode, map[i].RawValue);
|
||
|
|
ctx.BranchIfEqual(label3, @short: true);
|
||
|
|
ctx.Branch(label2, @short: true);
|
||
|
|
ctx.MarkLabel(label3);
|
||
|
|
ctx.LoadValue(map[i].WireValue);
|
||
|
|
ctx.EmitBasicWrite("WriteInt32", null);
|
||
|
|
ctx.Branch(label, @short: false);
|
||
|
|
ctx.MarkLabel(label2);
|
||
|
|
}
|
||
|
|
ctx.LoadReaderWriter();
|
||
|
|
ctx.LoadValue(local);
|
||
|
|
ctx.CastToObject(ExpectedType);
|
||
|
|
ctx.EmitCall(ctx.MapType(typeof(ProtoWriter)).GetMethod("ThrowEnumException"));
|
||
|
|
ctx.MarkLabel(label);
|
||
|
|
}
|
||
|
|
|
||
|
|
void IProtoSerializer.EmitRead(CompilerContext ctx, Local valueFrom)
|
||
|
|
{
|
||
|
|
ProtoTypeCode typeCode = GetTypeCode();
|
||
|
|
if (map == null)
|
||
|
|
{
|
||
|
|
ctx.EmitBasicRead("ReadInt32", ctx.MapType(typeof(int)));
|
||
|
|
ctx.ConvertFromInt32(typeCode, uint32Overflow: false);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
int[] array = new int[map.Length];
|
||
|
|
object[] array2 = new object[map.Length];
|
||
|
|
for (int i = 0; i < map.Length; i++)
|
||
|
|
{
|
||
|
|
array[i] = map[i].WireValue;
|
||
|
|
array2[i] = map[i].RawValue;
|
||
|
|
}
|
||
|
|
using Local local = new Local(ctx, ExpectedType);
|
||
|
|
using Local local2 = new Local(ctx, ctx.MapType(typeof(int)));
|
||
|
|
ctx.EmitBasicRead("ReadInt32", ctx.MapType(typeof(int)));
|
||
|
|
ctx.StoreValue(local2);
|
||
|
|
CodeLabel codeLabel = ctx.DefineLabel();
|
||
|
|
BasicList.NodeEnumerator enumerator = BasicList.GetContiguousGroups(array, array2).GetEnumerator();
|
||
|
|
while (enumerator.MoveNext())
|
||
|
|
{
|
||
|
|
BasicList.Group obj = (BasicList.Group)enumerator.Current;
|
||
|
|
CodeLabel label = ctx.DefineLabel();
|
||
|
|
int count = obj.Items.Count;
|
||
|
|
if (count == 1)
|
||
|
|
{
|
||
|
|
ctx.LoadValue(local2);
|
||
|
|
ctx.LoadValue(obj.First);
|
||
|
|
CodeLabel codeLabel2 = ctx.DefineLabel();
|
||
|
|
ctx.BranchIfEqual(codeLabel2, @short: true);
|
||
|
|
ctx.Branch(label, @short: false);
|
||
|
|
WriteEnumValue(ctx, typeCode, codeLabel2, codeLabel, obj.Items[0], local);
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
ctx.LoadValue(local2);
|
||
|
|
ctx.LoadValue(obj.First);
|
||
|
|
ctx.Subtract();
|
||
|
|
CodeLabel[] array3 = new CodeLabel[count];
|
||
|
|
for (int j = 0; j < count; j++)
|
||
|
|
{
|
||
|
|
array3[j] = ctx.DefineLabel();
|
||
|
|
}
|
||
|
|
ctx.Switch(array3);
|
||
|
|
ctx.Branch(label, @short: false);
|
||
|
|
for (int k = 0; k < count; k++)
|
||
|
|
{
|
||
|
|
WriteEnumValue(ctx, typeCode, array3[k], codeLabel, obj.Items[k], local);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
ctx.MarkLabel(label);
|
||
|
|
}
|
||
|
|
ctx.LoadReaderWriter();
|
||
|
|
ctx.LoadValue(ExpectedType);
|
||
|
|
ctx.LoadValue(local2);
|
||
|
|
ctx.EmitCall(ctx.MapType(typeof(ProtoReader)).GetMethod("ThrowEnumException"));
|
||
|
|
ctx.MarkLabel(codeLabel);
|
||
|
|
ctx.LoadValue(local);
|
||
|
|
}
|
||
|
|
|
||
|
|
private static void WriteEnumValue(CompilerContext ctx, ProtoTypeCode typeCode, object value)
|
||
|
|
{
|
||
|
|
switch (typeCode)
|
||
|
|
{
|
||
|
|
case ProtoTypeCode.Byte:
|
||
|
|
ctx.LoadValue((byte)value);
|
||
|
|
break;
|
||
|
|
case ProtoTypeCode.SByte:
|
||
|
|
ctx.LoadValue((sbyte)value);
|
||
|
|
break;
|
||
|
|
case ProtoTypeCode.Int16:
|
||
|
|
ctx.LoadValue((short)value);
|
||
|
|
break;
|
||
|
|
case ProtoTypeCode.Int32:
|
||
|
|
ctx.LoadValue((int)value);
|
||
|
|
break;
|
||
|
|
case ProtoTypeCode.Int64:
|
||
|
|
ctx.LoadValue((long)value);
|
||
|
|
break;
|
||
|
|
case ProtoTypeCode.UInt16:
|
||
|
|
ctx.LoadValue((ushort)value);
|
||
|
|
break;
|
||
|
|
case ProtoTypeCode.UInt32:
|
||
|
|
ctx.LoadValue((int)(uint)value);
|
||
|
|
break;
|
||
|
|
case ProtoTypeCode.UInt64:
|
||
|
|
ctx.LoadValue((long)(ulong)value);
|
||
|
|
break;
|
||
|
|
default:
|
||
|
|
throw new InvalidOperationException();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private static void WriteEnumValue(CompilerContext ctx, ProtoTypeCode typeCode, CodeLabel handler, CodeLabel @continue, object value, Local local)
|
||
|
|
{
|
||
|
|
ctx.MarkLabel(handler);
|
||
|
|
WriteEnumValue(ctx, typeCode, value);
|
||
|
|
ctx.StoreValue(local);
|
||
|
|
ctx.Branch(@continue, @short: false);
|
||
|
|
}
|
||
|
|
}
|