AyCode.Core/AyCode.Core.Serializers.Sou.../AcBinarySourceGenerator.cs

779 lines
34 KiB
C#

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
namespace AyCode.Core.Serializers.SourceGenerator;
/// <summary>
/// Source Generator for AcBinary serialization.
/// Generates optimized serialize/deserialize methods for classes marked with [AcBinarySerializable].
/// </summary>
[Generator]
public class AcBinarySourceGenerator : IIncrementalGenerator
{
private const string AcBinarySerializableAttributeName = "AyCode.Core.Serializers.Attributes.AcBinarySerializableAttribute";
public void Initialize(IncrementalGeneratorInitializationContext context)
{
// Find all classes with [AcBinarySerializable] attribute
var classDeclarations = context.SyntaxProvider
.ForAttributeWithMetadataName(
AcBinarySerializableAttributeName,
predicate: static (node, _) => node is ClassDeclarationSyntax || node is StructDeclarationSyntax,
transform: static (ctx, _) => GetClassInfo(ctx))
.Where(static info => info != null);
// Combine with compilation
var compilationAndClasses = context.CompilationProvider.Combine(classDeclarations.Collect());
// Generate source
context.RegisterSourceOutput(compilationAndClasses,
static (spc, source) => Execute(source.Left, source.Right, spc));
}
private static SerializableClassInfo GetClassInfo(GeneratorAttributeSyntaxContext context)
{
if (!(context.TargetSymbol is INamedTypeSymbol typeSymbol))
return null;
var namespaceName = typeSymbol.ContainingNamespace.IsGlobalNamespace
? string.Empty
: typeSymbol.ContainingNamespace.ToDisplayString();
var className = typeSymbol.Name;
var fullTypeName = typeSymbol.ToDisplayString();
var isStruct = typeSymbol.IsValueType;
// Check if this is a nested type
var isNestedType = typeSymbol.ContainingType != null;
// For nested types, we need the full containing type path for method signatures
// e.g. "OuterClass.InnerClass" instead of just "InnerClass"
var typeNameForSignature = isNestedType
? GetNestedTypeName(typeSymbol)
: className;
// Get all public properties with getter and setter
// DUPLICATED LOGIC: Same filtering as TypeMetadataBase.GetSerializableProperties()
var properties = new List<PropertyInfo>();
foreach (var member in typeSymbol.GetMembers())
{
if (member is IPropertySymbol p &&
p.DeclaredAccessibility == Accessibility.Public &&
p.GetMethod != null &&
p.SetMethod != null &&
!p.IsIndexer &&
!p.IsStatic)
{
properties.Add(new PropertyInfo(
p.Name,
p.Type.ToDisplayString(),
GetPropertyTypeKind(p.Type),
p.Type.NullableAnnotation == NullableAnnotation.Annotated || IsNullableValueType(p.Type)));
}
}
// DUPLICATED LOGIC: Same ordering as TypeMetadataBase.GetSerializableProperties()
properties.Sort((a, b) => string.Compare(a.Name, b.Name, StringComparison.Ordinal));
return new SerializableClassInfo(namespaceName, className, fullTypeName, isStruct, isNestedType, typeNameForSignature, properties);
}
/// <summary>
/// Gets the nested type name chain (e.g., "OuterClass.MiddleClass.InnerClass")
/// </summary>
private static string GetNestedTypeName(INamedTypeSymbol typeSymbol)
{
var parts = new List<string>();
var current = typeSymbol;
while (current != null)
{
parts.Insert(0, current.Name);
current = current.ContainingType;
}
return string.Join(".", parts);
}
private static bool IsNullableValueType(ITypeSymbol type)
{
return type is INamedTypeSymbol namedType &&
namedType.IsGenericType &&
namedType.ConstructedFrom.SpecialType == SpecialType.System_Nullable_T;
}
private static PropertyTypeKind GetPropertyTypeKind(ITypeSymbol type)
{
// Handle nullable value types
if (type is INamedTypeSymbol namedType && namedType.IsGenericType &&
namedType.ConstructedFrom.SpecialType == SpecialType.System_Nullable_T)
{
// Get underlying type
var underlyingType = namedType.TypeArguments[0];
return GetPropertyTypeKindForUnderlying(underlyingType, isNullable: true);
}
return GetPropertyTypeKindForUnderlying(type, isNullable: false);
}
private static PropertyTypeKind GetPropertyTypeKindForUnderlying(ITypeSymbol type, bool isNullable)
{
switch (type.SpecialType)
{
case SpecialType.System_String: return PropertyTypeKind.String;
case SpecialType.System_Int32: return isNullable ? PropertyTypeKind.NullableInt32 : PropertyTypeKind.Int32;
case SpecialType.System_Int64: return isNullable ? PropertyTypeKind.NullableInt64 : PropertyTypeKind.Int64;
case SpecialType.System_Int16: return isNullable ? PropertyTypeKind.NullableInt16 : PropertyTypeKind.Int16;
case SpecialType.System_Byte: return isNullable ? PropertyTypeKind.NullableByte : PropertyTypeKind.Byte;
case SpecialType.System_SByte: return isNullable ? PropertyTypeKind.NullableSByte : PropertyTypeKind.SByte;
case SpecialType.System_UInt16: return isNullable ? PropertyTypeKind.NullableUInt16 : PropertyTypeKind.UInt16;
case SpecialType.System_UInt32: return isNullable ? PropertyTypeKind.NullableUInt32 : PropertyTypeKind.UInt32;
case SpecialType.System_UInt64: return isNullable ? PropertyTypeKind.NullableUInt64 : PropertyTypeKind.UInt64;
case SpecialType.System_Boolean: return isNullable ? PropertyTypeKind.NullableBoolean : PropertyTypeKind.Boolean;
case SpecialType.System_Single: return isNullable ? PropertyTypeKind.NullableSingle : PropertyTypeKind.Single;
case SpecialType.System_Double: return isNullable ? PropertyTypeKind.NullableDouble : PropertyTypeKind.Double;
case SpecialType.System_Decimal: return isNullable ? PropertyTypeKind.NullableDecimal : PropertyTypeKind.Decimal;
case SpecialType.System_DateTime: return isNullable ? PropertyTypeKind.NullableDateTime : PropertyTypeKind.DateTime;
default: return GetNonSpecialTypeKind(type, isNullable);
}
}
private static PropertyTypeKind GetNonSpecialTypeKind(ITypeSymbol type, bool isNullable)
{
var fullName = type.ToDisplayString();
if (fullName == "System.Guid") return isNullable ? PropertyTypeKind.NullableGuid : PropertyTypeKind.Guid;
if (fullName == "System.TimeSpan") return isNullable ? PropertyTypeKind.NullableTimeSpan : PropertyTypeKind.TimeSpan;
if (fullName == "System.DateTimeOffset") return isNullable ? PropertyTypeKind.NullableDateTimeOffset : PropertyTypeKind.DateTimeOffset;
if (fullName == "System.DateOnly") return isNullable ? PropertyTypeKind.NullableDateOnly : PropertyTypeKind.DateOnly;
if (fullName == "System.TimeOnly") return isNullable ? PropertyTypeKind.NullableTimeOnly : PropertyTypeKind.TimeOnly;
if (type.TypeKind == TypeKind.Enum) return isNullable ? PropertyTypeKind.NullableEnum : PropertyTypeKind.Enum;
if (IsCollectionType(type)) return PropertyTypeKind.Collection;
if (type.TypeKind == TypeKind.Class || type.TypeKind == TypeKind.Struct) return PropertyTypeKind.Complex;
return PropertyTypeKind.Unknown;
}
private static bool IsCollectionType(ITypeSymbol type)
{
if (type is IArrayTypeSymbol)
return true;
if (type is INamedTypeSymbol namedType)
{
foreach (var iface in namedType.AllInterfaces)
{
if (iface.OriginalDefinition.SpecialType == SpecialType.System_Collections_Generic_IEnumerable_T)
return true;
var ifaceName = iface.ToDisplayString();
if (ifaceName.StartsWith("System.Collections.Generic.IList<") ||
ifaceName.StartsWith("System.Collections.Generic.ICollection<"))
return true;
}
}
return false;
}
private static void Execute(Compilation compilation, ImmutableArray<SerializableClassInfo> classes, SourceProductionContext context)
{
if (classes.IsDefaultOrEmpty)
return;
foreach (var classInfo in classes)
{
if (classInfo == null)
continue;
var source = GenerateSerializerClass(classInfo);
context.AddSource($"{classInfo.ClassName}_AcBinarySerializer.g.cs", SourceText.From(source, Encoding.UTF8));
}
}
private static string GenerateSerializerClass(SerializableClassInfo classInfo)
{
var sb = new StringBuilder();
sb.AppendLine("// <auto-generated/>");
sb.AppendLine("#nullable enable");
sb.AppendLine();
sb.AppendLine("using System;");
sb.AppendLine("using System.Runtime.CompilerServices;");
sb.AppendLine("using AyCode.Core.Serializers.Binaries;");
sb.AppendLine();
if (!string.IsNullOrEmpty(classInfo.Namespace))
{
sb.AppendLine($"namespace {classInfo.Namespace}");
sb.AppendLine("{");
}
var indent = string.IsNullOrEmpty(classInfo.Namespace) ? "" : " ";
sb.AppendLine($"{indent}/// <summary>");
sb.AppendLine($"{indent}/// Generated binary serializer for {classInfo.ClassName}.");
sb.AppendLine($"{indent}/// </summary>");
sb.AppendLine($"{indent}internal static class {classInfo.ClassName}_AcBinarySerializer");
sb.AppendLine($"{indent}{{");
// Generate property count constant
sb.AppendLine($"{indent} public const int PropertyCount = {classInfo.Properties.Count};");
sb.AppendLine();
// Generate property names array for validation
sb.AppendLine($"{indent} /// <summary>");
sb.AppendLine($"{indent} /// Property names in serialization order (alphabetical).");
sb.AppendLine($"{indent} /// Used for runtime validation against TypeMetadataBase.GetSerializableProperties().");
sb.AppendLine($"{indent} /// </summary>");
sb.Append($"{indent} public static readonly string[] PropertyNames = new[] {{ ");
sb.Append(string.Join(", ", classInfo.Properties.Select(p => $"\"{p.Name}\"")));
sb.AppendLine(" };");
sb.AppendLine();
// Generate Serialize method
GenerateSerializeMethod(sb, classInfo, indent);
// Generate Deserialize method
GenerateDeserializeMethod(sb, classInfo, indent);
sb.AppendLine($"{indent}}}");
if (!string.IsNullOrEmpty(classInfo.Namespace))
{
sb.AppendLine("}");
}
return sb.ToString();
}
private static void GenerateSerializeMethod(StringBuilder sb, SerializableClassInfo classInfo, string indent)
{
sb.AppendLine($"{indent} /// <summary>");
sb.AppendLine($"{indent} /// Serializes a {classInfo.ClassName} instance to the binary context.");
sb.AppendLine($"{indent} /// Direct property access - no reflection, no boxing for primitives.");
sb.AppendLine($"{indent} /// </summary>");
sb.AppendLine($"{indent} [MethodImpl(MethodImplOptions.AggressiveInlining)]");
sb.AppendLine($"{indent} public static void Serialize({classInfo.TypeNameForSignature} obj, ref AcBinarySerializer.BinarySerializationContext context)");
sb.AppendLine($"{indent} {{");
foreach (var prop in classInfo.Properties)
{
GenerateSerializeProperty(sb, prop, indent + " ");
}
sb.AppendLine($"{indent} }}");
sb.AppendLine();
}
private static void GenerateSerializeProperty(StringBuilder sb, PropertyInfo prop, string indent)
{
var propAccess = $"obj.{prop.Name}";
// Handle nullable VALUE types (Nullable<T>) - these use .HasValue and .Value
if (IsNullableValueTypeKind(prop.TypeKind))
{
sb.AppendLine($"{indent}// {prop.Name}: {prop.TypeName} (nullable value type)");
sb.AppendLine($"{indent}if ({propAccess}.HasValue)");
sb.AppendLine($"{indent}{{");
GenerateSerializeValue(sb, prop.TypeKind, $"{propAccess}.Value", indent + " ");
sb.AppendLine($"{indent}}}");
sb.AppendLine($"{indent}else");
sb.AppendLine($"{indent}{{");
sb.AppendLine($"{indent} context.WriteByte(BinaryTypeCode.Null);");
sb.AppendLine($"{indent}}}");
return;
}
sb.AppendLine($"{indent}// {prop.Name}: {prop.TypeName}");
// String needs null check
if (prop.TypeKind == PropertyTypeKind.String)
{
sb.AppendLine($"{indent}if ({propAccess} == null)");
sb.AppendLine($"{indent}{{");
sb.AppendLine($"{indent} context.WriteByte(BinaryTypeCode.Null);");
sb.AppendLine($"{indent}}}");
sb.AppendLine($"{indent}else if ({propAccess}.Length == 0)");
sb.AppendLine($"{indent}{{");
sb.AppendLine($"{indent} context.WriteByte(BinaryTypeCode.StringEmpty);");
sb.AppendLine($"{indent}}}");
sb.AppendLine($"{indent}else");
sb.AppendLine($"{indent}{{");
sb.AppendLine($"{indent} context.WriteByte(BinaryTypeCode.String);");
sb.AppendLine($"{indent} context.WriteStringUtf8({propAccess});");
sb.AppendLine($"{indent}}}");
return;
}
// Nullable reference types (Complex/Collection with ? annotation) - use == null
if (prop.IsNullable && (prop.TypeKind == PropertyTypeKind.Complex || prop.TypeKind == PropertyTypeKind.Collection))
{
sb.AppendLine($"{indent}if ({propAccess} == null)");
sb.AppendLine($"{indent}{{");
sb.AppendLine($"{indent} context.WriteByte(BinaryTypeCode.Null);");
sb.AppendLine($"{indent}}}");
sb.AppendLine($"{indent}else");
sb.AppendLine($"{indent}{{");
GenerateSerializeValue(sb, prop.TypeKind, propAccess, indent + " ");
sb.AppendLine($"{indent}}}");
return;
}
GenerateSerializeValue(sb, prop.TypeKind, propAccess, indent);
}
/// <summary>
/// Checks if the type kind represents a nullable VALUE type (Nullable&lt;T&gt;), not a reference type
/// </summary>
private static bool IsNullableValueTypeKind(PropertyTypeKind kind)
{
return kind >= PropertyTypeKind.NullableInt32 && kind <= PropertyTypeKind.NullableEnum;
}
private static void GenerateSerializeValue(StringBuilder sb, PropertyTypeKind typeKind, string valueExpr, string indent)
{
switch (typeKind)
{
case PropertyTypeKind.Int32:
case PropertyTypeKind.NullableInt32:
sb.AppendLine($"{indent}if (BinaryTypeCode.TryEncodeTinyInt({valueExpr}, out var tiny_{valueExpr.Replace(".", "_")}))");
sb.AppendLine($"{indent}{{");
sb.AppendLine($"{indent} context.WriteByte(tiny_{valueExpr.Replace(".", "_")});");
sb.AppendLine($"{indent}}}");
sb.AppendLine($"{indent}else");
sb.AppendLine($"{indent}{{");
sb.AppendLine($"{indent} context.WriteByte(BinaryTypeCode.Int32);");
sb.AppendLine($"{indent} context.WriteVarInt({valueExpr});");
sb.AppendLine($"{indent}}}");
break;
case PropertyTypeKind.Int64:
case PropertyTypeKind.NullableInt64:
sb.AppendLine($"{indent}context.WriteByte(BinaryTypeCode.Int64);");
sb.AppendLine($"{indent}context.WriteVarLong({valueExpr});");
break;
case PropertyTypeKind.Boolean:
case PropertyTypeKind.NullableBoolean:
sb.AppendLine($"{indent}context.WriteByte({valueExpr} ? BinaryTypeCode.True : BinaryTypeCode.False);");
break;
case PropertyTypeKind.Double:
case PropertyTypeKind.NullableDouble:
sb.AppendLine($"{indent}context.WriteTypeCodeAndRaw(BinaryTypeCode.Float64, {valueExpr});");
break;
case PropertyTypeKind.Single:
case PropertyTypeKind.NullableSingle:
sb.AppendLine($"{indent}context.WriteTypeCodeAndRaw(BinaryTypeCode.Float32, {valueExpr});");
break;
case PropertyTypeKind.Decimal:
case PropertyTypeKind.NullableDecimal:
sb.AppendLine($"{indent}context.WriteByte(BinaryTypeCode.Decimal);");
sb.AppendLine($"{indent}context.WriteDecimalBits({valueExpr});");
break;
case PropertyTypeKind.DateTime:
case PropertyTypeKind.NullableDateTime:
sb.AppendLine($"{indent}context.WriteByte(BinaryTypeCode.DateTime);");
sb.AppendLine($"{indent}context.WriteDateTimeBits({valueExpr});");
break;
case PropertyTypeKind.Guid:
case PropertyTypeKind.NullableGuid:
sb.AppendLine($"{indent}context.WriteByte(BinaryTypeCode.Guid);");
sb.AppendLine($"{indent}context.WriteGuidBits({valueExpr});");
break;
case PropertyTypeKind.Byte:
case PropertyTypeKind.NullableByte:
sb.AppendLine($"{indent}context.WriteTwoBytes(BinaryTypeCode.UInt8, {valueExpr});");
break;
case PropertyTypeKind.Int16:
case PropertyTypeKind.NullableInt16:
sb.AppendLine($"{indent}context.WriteTypeCodeAndRaw(BinaryTypeCode.Int16, {valueExpr});");
break;
case PropertyTypeKind.UInt16:
case PropertyTypeKind.NullableUInt16:
sb.AppendLine($"{indent}context.WriteTypeCodeAndRaw(BinaryTypeCode.UInt16, {valueExpr});");
break;
case PropertyTypeKind.UInt32:
case PropertyTypeKind.NullableUInt32:
sb.AppendLine($"{indent}context.WriteByte(BinaryTypeCode.UInt32);");
sb.AppendLine($"{indent}context.WriteVarUInt({valueExpr});");
sb.AppendLine($"{indent}context.WriteByte(BinaryTypeCode.UInt32);");
sb.AppendLine($"{indent}context.WriteVarUInt({valueExpr});");
break;
case PropertyTypeKind.UInt64:
case PropertyTypeKind.NullableUInt64:
sb.AppendLine($"{indent}context.WriteByte(BinaryTypeCode.UInt64);");
sb.AppendLine($"{indent}context.WriteVarULong({valueExpr});");
break;
case PropertyTypeKind.TimeSpan:
case PropertyTypeKind.NullableTimeSpan:
sb.AppendLine($"{indent}context.WriteTypeCodeAndRaw(BinaryTypeCode.TimeSpan, {valueExpr}.Ticks);");
break;
case PropertyTypeKind.DateTimeOffset:
case PropertyTypeKind.NullableDateTimeOffset:
sb.AppendLine($"{indent}context.WriteByte(BinaryTypeCode.DateTimeOffset);");
sb.AppendLine($"{indent}context.WriteDateTimeOffsetBits({valueExpr});");
break;
case PropertyTypeKind.Enum:
case PropertyTypeKind.NullableEnum:
sb.AppendLine($"{indent}context.WriteByte(BinaryTypeCode.Enum);");
sb.AppendLine($"{indent}var enumVal = (int){valueExpr};");
sb.AppendLine($"{indent}if (BinaryTypeCode.TryEncodeTinyInt(enumVal, out var tinyEnum))");
sb.AppendLine($"{indent}{{");
sb.AppendLine($"{indent} context.WriteByte(tinyEnum);");
sb.AppendLine($"{indent}}}");
sb.AppendLine($"{indent}else");
sb.AppendLine($"{indent}{{");
sb.AppendLine($"{indent} context.WriteByte(BinaryTypeCode.Int32);");
sb.AppendLine($"{indent} context.WriteVarInt(enumVal);");
sb.AppendLine($"{indent}}}");
break;
case PropertyTypeKind.Collection:
case PropertyTypeKind.Complex:
// TODO: Collections and complex types will be implemented later
sb.AppendLine($"{indent}// TODO: Complex/Collection type - fallback to runtime serializer");
sb.AppendLine($"{indent}throw new NotImplementedException(\"Complex/Collection types not yet implemented in generated serializer\");");
break;
default:
sb.AppendLine($"{indent}// Unknown type - fallback needed");
sb.AppendLine($"{indent}throw new NotImplementedException($\"Type {typeKind} not implemented in generated serializer\");");
break;
}
}
private static void GenerateDeserializeMethod(StringBuilder sb, SerializableClassInfo classInfo, string indent)
{
sb.AppendLine($"{indent} /// <summary>");
sb.AppendLine($"{indent} /// Deserializes properties into a {classInfo.ClassName} instance from the binary context.");
sb.AppendLine($"{indent} /// Direct property access - no reflection, no boxing for primitives.");
sb.AppendLine($"{indent} /// </summary>");
sb.AppendLine($"{indent} [MethodImpl(MethodImplOptions.AggressiveInlining)]");
sb.AppendLine($"{indent} public static void Deserialize({classInfo.TypeNameForSignature} obj, ref AcBinaryDeserializer.BinaryDeserializationContext context)");
sb.AppendLine($"{indent} {{");
foreach (var prop in classInfo.Properties)
{
GenerateDeserializeProperty(sb, prop, indent + " ");
}
sb.AppendLine($"{indent} }}");
}
private static void GenerateDeserializeProperty(StringBuilder sb, PropertyInfo prop, string indent)
{
sb.AppendLine($"{indent}// {prop.Name}: {prop.TypeName}");
sb.AppendLine($"{indent}{{");
sb.AppendLine($"{indent} var peekCode = context.PeekByte();");
// Handle Skip marker
sb.AppendLine($"{indent} if (peekCode == BinaryTypeCode.PropertySkip)");
sb.AppendLine($"{indent} {{");
sb.AppendLine($"{indent} context.ReadByte(); // consume Skip marker");
sb.AppendLine($"{indent} // Property keeps default value");
sb.AppendLine($"{indent} }}");
// Handle Null marker
sb.AppendLine($"{indent} else if (peekCode == BinaryTypeCode.Null)");
sb.AppendLine($"{indent} {{");
sb.AppendLine($"{indent} context.ReadByte(); // consume Null marker");
if (prop.IsNullable || prop.TypeKind == PropertyTypeKind.String)
{
sb.AppendLine($"{indent} obj.{prop.Name} = default;");
}
else
{
sb.AppendLine($"{indent} // Non-nullable property, keep default");
}
sb.AppendLine($"{indent} }}");
// Handle actual value
sb.AppendLine($"{indent} else");
sb.AppendLine($"{indent} {{");
GenerateDeserializeValue(sb, prop, indent + " ");
sb.AppendLine($"{indent} }}");
sb.AppendLine($"{indent}}}");
}
private static void GenerateDeserializeValue(StringBuilder sb, PropertyInfo prop, string indent)
{
var propAccess = $"obj.{prop.Name}";
switch (prop.TypeKind)
{
case PropertyTypeKind.String:
sb.AppendLine($"{indent}if (peekCode == BinaryTypeCode.StringEmpty)");
sb.AppendLine($"{indent}{{");
sb.AppendLine($"{indent} context.ReadByte();");
sb.AppendLine($"{indent} {propAccess} = string.Empty;");
sb.AppendLine($"{indent}}}");
sb.AppendLine($"{indent}else if (peekCode == BinaryTypeCode.String)");
sb.AppendLine($"{indent}{{");
sb.AppendLine($"{indent} context.ReadByte();");
sb.AppendLine($"{indent} var len = (int)context.ReadVarUInt();");
sb.AppendLine($"{indent} {propAccess} = context.ReadStringUtf8(len);");
sb.AppendLine($"{indent}}}");
sb.AppendLine($"{indent}else if (BinaryTypeCode.IsFixStr(peekCode))");
sb.AppendLine($"{indent}{{");
sb.AppendLine($"{indent} context.ReadByte();");
sb.AppendLine($"{indent} var len = BinaryTypeCode.DecodeFixStrLength(peekCode);");
sb.AppendLine($"{indent} {propAccess} = len == 0 ? string.Empty : context.ReadStringUtf8(len);");
sb.AppendLine($"{indent}}}");
break;
case PropertyTypeKind.Int32:
case PropertyTypeKind.NullableInt32:
sb.AppendLine($"{indent}if (BinaryTypeCode.IsTinyInt(peekCode))");
sb.AppendLine($"{indent}{{");
sb.AppendLine($"{indent} context.ReadByte();");
sb.AppendLine($"{indent} {propAccess} = BinaryTypeCode.DecodeTinyInt(peekCode);");
sb.AppendLine($"{indent}}}");
sb.AppendLine($"{indent}else if (peekCode == BinaryTypeCode.Int32)");
sb.AppendLine($"{indent}{{");
sb.AppendLine($"{indent} context.ReadByte();");
sb.AppendLine($"{indent} {propAccess} = context.ReadVarInt();");
sb.AppendLine($"{indent}}}");
break;
case PropertyTypeKind.Int64:
case PropertyTypeKind.NullableInt64:
sb.AppendLine($"{indent}if (BinaryTypeCode.IsTinyInt(peekCode))");
sb.AppendLine($"{indent}{{");
sb.AppendLine($"{indent} context.ReadByte();");
sb.AppendLine($"{indent} {propAccess} = BinaryTypeCode.DecodeTinyInt(peekCode);");
sb.AppendLine($"{indent}}}");
sb.AppendLine($"{indent}else if (peekCode == BinaryTypeCode.Int32)");
sb.AppendLine($"{indent}{{");
sb.AppendLine($"{indent} context.ReadByte();");
sb.AppendLine($"{indent} {propAccess} = context.ReadVarInt();");
sb.AppendLine($"{indent}}}");
sb.AppendLine($"{indent}else if (peekCode == BinaryTypeCode.Int64)");
sb.AppendLine($"{indent}{{");
sb.AppendLine($"{indent} context.ReadByte();");
sb.AppendLine($"{indent} {propAccess} = context.ReadVarLong();");
sb.AppendLine($"{indent}}}");
break;
case PropertyTypeKind.Boolean:
case PropertyTypeKind.NullableBoolean:
sb.AppendLine($"{indent}context.ReadByte();");
sb.AppendLine($"{indent}{propAccess} = peekCode == BinaryTypeCode.True;");
break;
case PropertyTypeKind.Double:
case PropertyTypeKind.NullableDouble:
sb.AppendLine($"{indent}context.ReadByte();");
sb.AppendLine($"{indent}{propAccess} = context.ReadDoubleUnsafe();");
break;
case PropertyTypeKind.Single:
case PropertyTypeKind.NullableSingle:
sb.AppendLine($"{indent}context.ReadByte();");
sb.AppendLine($"{indent}{propAccess} = context.ReadSingleUnsafe();");
break;
case PropertyTypeKind.Decimal:
case PropertyTypeKind.NullableDecimal:
sb.AppendLine($"{indent}context.ReadByte();");
sb.AppendLine($"{indent}{propAccess} = context.ReadDecimalUnsafe();");
break;
case PropertyTypeKind.DateTime:
case PropertyTypeKind.NullableDateTime:
sb.AppendLine($"{indent}context.ReadByte();");
sb.AppendLine($"{indent}{propAccess} = context.ReadDateTimeUnsafe();");
break;
case PropertyTypeKind.Guid:
case PropertyTypeKind.NullableGuid:
sb.AppendLine($"{indent}context.ReadByte();");
sb.AppendLine($"{indent}{propAccess} = context.ReadGuidUnsafe();");
break;
case PropertyTypeKind.Byte:
case PropertyTypeKind.NullableByte:
sb.AppendLine($"{indent}if (BinaryTypeCode.IsTinyInt(peekCode))");
sb.AppendLine($"{indent}{{");
sb.AppendLine($"{indent} context.ReadByte();");
sb.AppendLine($"{indent} {propAccess} = (byte)BinaryTypeCode.DecodeTinyInt(peekCode);");
sb.AppendLine($"{indent}}}");
sb.AppendLine($"{indent}else");
sb.AppendLine($"{indent}{{");
sb.AppendLine($"{indent} context.ReadByte();");
sb.AppendLine($"{indent} {propAccess} = context.ReadByte();");
sb.AppendLine($"{indent}}}");
break;
case PropertyTypeKind.Int16:
case PropertyTypeKind.NullableInt16:
sb.AppendLine($"{indent}context.ReadByte();");
sb.AppendLine($"{indent}{propAccess} = context.ReadInt16Unsafe();");
break;
case PropertyTypeKind.UInt16:
case PropertyTypeKind.NullableUInt16:
sb.AppendLine($"{indent}context.ReadByte();");
sb.AppendLine($"{indent}{propAccess} = context.ReadUInt16Unsafe();");
break;
case PropertyTypeKind.UInt32:
case PropertyTypeKind.NullableUInt32:
sb.AppendLine($"{indent}context.ReadByte();");
sb.AppendLine($"{indent}{propAccess} = context.ReadVarUInt();");
break;
case PropertyTypeKind.UInt64:
case PropertyTypeKind.NullableUInt64:
sb.AppendLine($"{indent}context.ReadByte();");
sb.AppendLine($"{indent}{propAccess} = context.ReadVarULong();");
break;
case PropertyTypeKind.TimeSpan:
case PropertyTypeKind.NullableTimeSpan:
sb.AppendLine($"{indent}context.ReadByte();");
sb.AppendLine($"{indent}{propAccess} = context.ReadTimeSpanUnsafe();");
break;
case PropertyTypeKind.DateTimeOffset:
case PropertyTypeKind.NullableDateTimeOffset:
sb.AppendLine($"{indent}context.ReadByte();");
sb.AppendLine($"{indent}{propAccess} = context.ReadDateTimeOffsetUnsafe();");
break;
case PropertyTypeKind.Enum:
case PropertyTypeKind.NullableEnum:
sb.AppendLine($"{indent}// TODO: Enum deserialization needs type info");
sb.AppendLine($"{indent}throw new NotImplementedException(\"Enum deserialization not yet implemented\");");
break;
case PropertyTypeKind.Collection:
case PropertyTypeKind.Complex:
sb.AppendLine($"{indent}// TODO: Complex/Collection types");
sb.AppendLine($"{indent}throw new NotImplementedException(\"Complex/Collection deserialization not yet implemented\");");
break;
default:
sb.AppendLine($"{indent}throw new NotImplementedException($\"Type deserialization not implemented\");");
break;
}
}
}
/// <summary>
/// Information about a class marked with [AcBinarySerializable].
/// </summary>
internal sealed class SerializableClassInfo
{
public string Namespace { get; }
public string ClassName { get; }
public string FullTypeName { get; }
public bool IsStruct { get; }
public bool IsNestedType { get; }
/// <summary>
/// The type name to use in method signatures. For nested types this includes the containing types.
/// e.g., "OuterClass.InnerClass"
/// </summary>
public string TypeNameForSignature { get; }
public List<PropertyInfo> Properties { get; }
public SerializableClassInfo(string ns, string className, string fullTypeName, bool isStruct, bool isNestedType, string typeNameForSignature, List<PropertyInfo> properties)
{
Namespace = ns;
ClassName = className;
FullTypeName = fullTypeName;
IsStruct = isStruct;
IsNestedType = isNestedType;
TypeNameForSignature = typeNameForSignature;
Properties = properties;
}
}
/// <summary>
/// Information about a serializable property.
/// </summary>
internal sealed class PropertyInfo
{
public string Name { get; }
public string TypeName { get; }
public PropertyTypeKind TypeKind { get; }
public bool IsNullable { get; }
public PropertyInfo(string name, string typeName, PropertyTypeKind typeKind, bool isNullable)
{
Name = name;
TypeName = typeName;
TypeKind = typeKind;
IsNullable = isNullable;
}
}
/// <summary>
/// Kind of property type for code generation.
/// </summary>
internal enum PropertyTypeKind
{
Unknown,
String,
Int32,
Int64,
Int16,
Byte,
SByte,
UInt16,
UInt32,
UInt64,
Boolean,
Single,
Double,
Decimal,
DateTime,
DateTimeOffset,
TimeSpan,
DateOnly,
TimeOnly,
Guid,
Enum,
Collection,
Complex,
// Nullable variants
NullableInt32,
NullableInt64,
NullableInt16,
NullableByte,
NullableSByte,
NullableUInt16,
NullableUInt32,
NullableUInt64,
NullableBoolean,
NullableSingle,
NullableDouble,
NullableDecimal,
NullableDateTime,
NullableDateTimeOffset,
NullableTimeSpan,
NullableDateOnly,
NullableTimeOnly,
NullableGuid,
NullableEnum
}