private void WriteSerializerClasses(SourceGeneratorContext context, List <INamedTypeSymbol> bitStructClasses, Dictionary <INamedTypeSymbol, ClassSerializeSizeInfo> classesSizeInfo, INamedTypeSymbol bitStructAttributeSymbol, INamedTypeSymbol bitArrayAttributeSymbol) { foreach (INamedTypeSymbol classSymbol in bitStructClasses) { ClassSerializeSizeInfo classSizeInfo = classesSizeInfo[classSymbol]; string classFullName = SourceGenUtils.GetTypeFullName(classSymbol); string serializerClassName = CreateSerializerName(classSymbol); BitStructAttribute bitStructAttribute = SourceGenUtils.GetAttribute <BitStructAttribute>(classSymbol, bitStructAttributeSymbol); var sourceBuilder = new StringBuilder(); sourceBuilder.Append($@" namespace {classSymbol.ContainingNamespace} {{ "); ITypeSymbol[] classContainingTypes = SourceGenUtils.GetContainingTypesList(classSymbol); foreach (ITypeSymbol containingType in classContainingTypes) { switch (containingType.TypeKind) { case TypeKind.Class: sourceBuilder.Append($@" partial class {containingType.Name} {{ "); break; case TypeKind.Struct: sourceBuilder.Append($@" partial struct {containingType.Name} {{ "); break; default: context.ReportDiagnostic(Diagnostic.Create(new DiagnosticDescriptor("TMP", "TMP", $"Only expecting struct or class containing types. Have {containingType.TypeKind}.", "TMP", DiagnosticSeverity.Error, true), Location.Create("TMP", new TextSpan(), new LinePositionSpan()))); return; } } sourceBuilder.Append($@" {SourceGenUtils.GetAccessibilityString(classSymbol.DeclaredAccessibility)} static class {serializerClassName} {{ "); var sizeFuncBuilder = new StringBuilder(); var serializeFuncBuilder = new StringBuilder(); var deserializeFuncBuilder = new StringBuilder(); if (classSizeInfo.Type == ClassSerializeSizeType.Const) { sizeFuncBuilder.Append($@" public const int Size = {classSizeInfo.ConstSize}; "); } sizeFuncBuilder.Append($@" public static int CalculateSize({classFullName} value) {{ int result = {classSizeInfo.ConstSize}; "); serializeFuncBuilder.Append($@" public static global::System.Span<byte> Serialize(global::System.Span<byte> output, {classFullName} value) {{ "); deserializeFuncBuilder.Append($@" public static global::System.ReadOnlySpan<byte> Deserialize(global::System.ReadOnlySpan<byte> input, out {classFullName} value) {{ value = new {classFullName}(); "); foreach (IFieldSymbol classFieldMember in GetClassFieldMembers(classSymbol)) { if (classFieldMember.Type.IsIntegerType() || classFieldMember.Type.TypeKind == TypeKind.Enum) { INamedTypeSymbol fieldType = (INamedTypeSymbol)classFieldMember.Type; IntegerOrEnumTypeInfo fieldTypeInfo = GetIntegerOrEnumTypeInfo(fieldType, bitStructAttribute.Endianess); serializeFuncBuilder.Append($@" if (!{fieldTypeInfo.SerializeFuncName}(output, {fieldTypeInfo.SerializeTypeCast}value.{classFieldMember.Name})) {{ throw new global::System.Exception(string.Format(""Not enough space to serialize field {{0}} from type {{1}}."", ""{classFieldMember.Name}"", ""{classSymbol.Name}"")); }} output = output.Slice({fieldTypeInfo.TypeSize}); "); deserializeFuncBuilder.Append($@" {{ if (!{fieldTypeInfo.DeserializeFuncName}(input, out var fieldValue)) {{ throw new global::System.Exception(string.Format(""Not enough data to deserialize field {{0}} from type {{1}}."", ""{classFieldMember.Name}"", ""{classSymbol.Name}"")); }} value.{classFieldMember.Name} = {fieldTypeInfo.DeserializeTypeCast}fieldValue; input = input.Slice({fieldTypeInfo.TypeSize}); }} "); } else if (classFieldMember.Type.TypeKind == TypeKind.Class || classFieldMember.Type.TypeKind == TypeKind.Struct) { if (!SourceGenUtils.HasAttribute(classFieldMember.Type, bitStructAttributeSymbol)) { // Type requires BitStruct attribute. return; } INamedTypeSymbol fieldType = (INamedTypeSymbol)classFieldMember.Type; ClassSerializeSizeInfo fieldTypeSizeInfo = classesSizeInfo[fieldType]; string serializerClassFullName = CreateSerializerFullName(fieldType); if (fieldTypeSizeInfo.Type == ClassSerializeSizeType.Dynamic) { sizeFuncBuilder.Append($@" result += {serializerClassFullName}.CalculateSize(value.{classFieldMember.Name}); "); } serializeFuncBuilder.Append($@" output = {serializerClassFullName}.Serialize(output, value.{classFieldMember.Name}); "); deserializeFuncBuilder.Append($@" {{ input = {serializerClassFullName}.Deserialize(input, out var fieldValue); value.{classFieldMember.Name} = fieldValue; }} "); } else if (classFieldMember.Type.TypeKind == TypeKind.Array) { BitArrayAttribute bitArrayAttribute = SourceGenUtils.GetAttribute <BitArrayAttribute>(classFieldMember, bitArrayAttributeSymbol); if (bitArrayAttribute == null) { // Type requires BitArray attribute. return; } IArrayTypeSymbol arrayType = (IArrayTypeSymbol)classFieldMember.Type; string elementTypeFullName = SourceGenUtils.GetTypeFullName(arrayType.ElementType); ClassSerializeSizeInfo elementTypeSizeInfo; string calculateElementSize; string serializeItem; string deserializeItem; if (arrayType.ElementType.IsIntegerType() || arrayType.ElementType.TypeKind == TypeKind.Enum) { IntegerOrEnumTypeInfo elementTypeInfo = GetIntegerOrEnumTypeInfo((INamedTypeSymbol)arrayType.ElementType, bitStructAttribute.Endianess); elementTypeSizeInfo = new ClassSerializeSizeInfo() { Type = ClassSerializeSizeType.Const, ConstSize = elementTypeInfo.TypeSize, }; calculateElementSize = $"result += {elementTypeInfo.TypeSize};"; serializeItem = $@" if (!{elementTypeInfo.SerializeFuncName}(output, {elementTypeInfo.SerializeTypeCast}item)) {{ throw new global::System.Exception(string.Format(""Not enough space to serialize item from list {{0}} from type {{1}}."", ""{classFieldMember.Name}"", ""{classSymbol.Name}"")); }} output = output.Slice({elementTypeInfo.TypeSize}); "; deserializeItem = $@" if (!{elementTypeInfo.DeserializeFuncName}(input, out var tmp)) {{ throw new global::System.Exception(string.Format(""Not enough data to deserialize item from list {{0}} from type {{1}}."", ""{classFieldMember.Name}"", ""{classSymbol.Name}"")); }} var item = {elementTypeInfo.DeserializeTypeCast}tmp; input = input.Slice({elementTypeInfo.TypeSize}); "; } else if (arrayType.ElementType.TypeKind == TypeKind.Class || arrayType.ElementType.TypeKind == TypeKind.Struct) { INamedTypeSymbol elementType = (INamedTypeSymbol)arrayType.ElementType; elementTypeSizeInfo = classesSizeInfo[elementType]; string elementSerializerClassFullName = CreateSerializerFullName(elementType); if (elementTypeSizeInfo.Type == ClassSerializeSizeType.Const) { calculateElementSize = $@"result += {elementSerializerClassFullName}.Size;"; } else { calculateElementSize = $@"result += {elementSerializerClassFullName}.CalculateSize(item);"; } serializeItem = $@"output = {elementSerializerClassFullName}.Serialize(output, item);"; deserializeItem = $@"input = {elementSerializerClassFullName}.Deserialize(input, out var item);"; } else { // Can't serialize type. return; } switch (bitArrayAttribute.SizeType) { case BitArraySizeType.Const: if (elementTypeSizeInfo.Type == ClassSerializeSizeType.Dynamic) { sizeFuncBuilder.Append($@" {{ var array = value.{classFieldMember.Name}; int collectionCount = array?.Length ?? 0; if (collectionCount > {bitArrayAttribute.ConstSize}) {{ throw new global::System.Exception(string.Format($""Constant size list {{0}} from type {{1}} has too many items."", ""{classFieldMember.Name}"", ""{classSymbol.Name}"")); }} if (array != null) {{ foreach (var item in array) {{ {calculateElementSize} }} }} int backfillCount = {bitArrayAttribute.ConstSize} - collectionCount; if (backfillCount > 0) {{ {elementTypeFullName} item = default; for (int i = 0; i != backfillCount; ++i) {{ {calculateElementSize} }} }} }} "); } serializeFuncBuilder.Append($@" {{ var array = value.{classFieldMember.Name}; int collectionCount = array?.Length ?? 0; if (collectionCount > {bitArrayAttribute.ConstSize}) {{ throw new global::System.Exception(string.Format($""Constant size list {{0}} from type {{1}} has too many items."", ""{classFieldMember.Name}"", ""{classSymbol.Name}"")); }} if (array != null) {{ foreach (var item in array) {{ {serializeItem} }} }} int backfillCount = {bitArrayAttribute.ConstSize} - collectionCount; if (backfillCount > 0) {{ {elementTypeFullName} item = default; for (int i = 0; i != backfillCount; ++i) {{ {serializeItem} }} }} }} "); deserializeFuncBuilder.Append($@" {{ var array = new {elementTypeFullName}[{bitArrayAttribute.ConstSize}]; for (int i = 0; i != {bitArrayAttribute.ConstSize}; ++i) {{ {deserializeItem} array[i] = item; }} value.{classFieldMember.Name} = array; }} "); break; case BitArraySizeType.EndFill: sizeFuncBuilder.Append($@" {{ var array = value.{classFieldMember.Name}; if (array != null) {{ foreach (var item in array) {{ {calculateElementSize} }} }} }} "); serializeFuncBuilder.Append($@" {{ var array = value.{classFieldMember.Name}; if (array != null) {{ foreach (var item in array) {{ {serializeItem} }} }} }} "); deserializeFuncBuilder.Append($@" var list = new global::System.Collections.Generic.List<{elementTypeFullName}>(); while (!input.IsEmpty) {{ {deserializeItem} list.Add(item); }} value.{classFieldMember.Name} = list.ToArray(); "); break; default: // Unknown BitArraySizeType. return; } } else { // Can't serialize type. return; } } sizeFuncBuilder.Append($@" return result; }} "); serializeFuncBuilder.Append($@" return output; }} "); deserializeFuncBuilder.Append($@" return input; }} "); sourceBuilder.Append(sizeFuncBuilder.ToString()); sourceBuilder.Append(serializeFuncBuilder.ToString()); sourceBuilder.Append(deserializeFuncBuilder.ToString()); for (int i = 0; i != classContainingTypes.Length + 1; ++i) { sourceBuilder.Append($@" }} "); } sourceBuilder.Append($@" }} "); string sourceCode = sourceBuilder.ToString(); context.AddSource($"{serializerClassName}.cs", SourceText.From(sourceCode, Encoding.UTF8)); } }
private static IntegerOrEnumTypeInfo GetIntegerOrEnumTypeInfo(INamedTypeSymbol typeSymbol, BitEndianess endianess) { IntegerOrEnumTypeInfo result; string endianessName = endianess == BitEndianess.BigEndian ? "BigEndian" : "LittleEndian"; ITypeSymbol fieldUnderlyingType = typeSymbol; result.SerializeTypeCast = string.Empty; result.DeserializeTypeCast = string.Empty; if (typeSymbol.TypeKind == TypeKind.Enum) { fieldUnderlyingType = typeSymbol.EnumUnderlyingType; result.SerializeTypeCast = $"({SourceGenUtils.GetTypeFullName(fieldUnderlyingType)})"; result.DeserializeTypeCast = $"({SourceGenUtils.GetTypeFullName(typeSymbol)})"; } switch (fieldUnderlyingType.SpecialType) { case SpecialType.System_Byte: case SpecialType.System_SByte: result.SerializeFuncName = $"global::BitSerialization.Generated.BitPrimitivesSerializer.TryWrite{fieldUnderlyingType.Name}"; result.DeserializeFuncName = $"global::BitSerialization.Generated.BitPrimitivesSerializer.TryRead{fieldUnderlyingType.Name}"; break; case SpecialType.System_UInt16: case SpecialType.System_Int16: case SpecialType.System_UInt32: case SpecialType.System_Int32: case SpecialType.System_UInt64: case SpecialType.System_Int64: result.SerializeFuncName = $"global::System.Buffers.Binary.BinaryPrimitives.TryWrite{fieldUnderlyingType.Name}{endianessName}"; result.DeserializeFuncName = $"global::System.Buffers.Binary.BinaryPrimitives.TryRead{fieldUnderlyingType.Name}{endianessName}"; break; default: throw new Exception(); } switch (fieldUnderlyingType.SpecialType) { case SpecialType.System_Byte: case SpecialType.System_SByte: result.TypeSize = 1; break; case SpecialType.System_UInt16: case SpecialType.System_Int16: result.TypeSize = 2; break; case SpecialType.System_UInt32: case SpecialType.System_Int32: result.TypeSize = 4; break; case SpecialType.System_UInt64: case SpecialType.System_Int64: result.TypeSize = 8; break; default: throw new Exception(); } return(result); }