M Core/NosSmooth.PacketSerializersGenerator/AttributeGenerators/IParameterGenerator.cs => Core/NosSmooth.PacketSerializersGenerator/AttributeGenerators/IParameterGenerator.cs +7 -8
@@ 6,6 6,7 @@
using System.CodeDom.Compiler;
using Microsoft.CodeAnalysis.CSharp.Syntax;
+using NosSmooth.PacketSerializersGenerator.Data;
using NosSmooth.PacketSerializersGenerator.Errors;
namespace NosSmooth.PacketSerializersGenerator.AttributeGenerators
@@ 18,26 19,24 @@ namespace NosSmooth.PacketSerializersGenerator.AttributeGenerators
/// <summary>
/// Check whether this generator should handle parameter with this attribute.
/// </summary>
- /// <param name="attribute">The parameters attribute.</param>
+ /// <param name="parameter">The parameter.</param>
/// <returns>Whether to handle this parameter.</returns>
- public bool ShouldHandle(AttributeSyntax attribute);
+ public bool ShouldHandle(ParameterInfo parameter);
/// <summary>
/// Generate part for the Serializer method to serialize the given parameter.
/// </summary>
/// <param name="textWriter">The text writer to write the code to.</param>
- /// <param name="recordDeclarationSyntax">The packet record declaration syntax.</param>
- /// <param name="parameterInfo">The parameter info to generate for.</param>
+ /// <param name="packetInfo">The packet info to generate for.</param>
/// <returns>The generated source code.</returns>
- public IError? GenerateSerializerPart(IndentedTextWriter textWriter, RecordDeclarationSyntax recordDeclarationSyntax, ParameterInfo parameterInfo);
+ public IError? GenerateSerializerPart(IndentedTextWriter textWriter, PacketInfo packetInfo);
/// <summary>
/// Generate part for the Deserializer method to deserialize the given parameter.
/// </summary>
/// <param name="textWriter">The text writer to write the code to.</param>
- /// <param name="recordDeclarationSyntax">The packet record declaration syntax.</param>
- /// <param name="parameterInfo">The parameter info to generate for.</param>
+ /// <param name="packetInfo">The packet info to generate for.</param>
/// <returns>The generated source code.</returns>
- public IError? GenerateDeserializerPart(IndentedTextWriter textWriter, RecordDeclarationSyntax recordDeclarationSyntax, ParameterInfo parameterInfo);
+ public IError? GenerateDeserializerPart(IndentedTextWriter textWriter, PacketInfo packetInfo);
}
}=
\ No newline at end of file
M Core/NosSmooth.PacketSerializersGenerator/AttributeGenerators/PacketContextListAttributeGenerator.cs => Core/NosSmooth.PacketSerializersGenerator/AttributeGenerators/PacketContextListAttributeGenerator.cs +41 -69
@@ 8,108 8,80 @@ using System.CodeDom.Compiler;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
+using NosSmooth.PacketSerializersGenerator.Data;
using NosSmooth.PacketSerializersGenerator.Errors;
+using NosSmooth.PacketSerializersGenerator.Extensions;
namespace NosSmooth.PacketSerializersGenerator.AttributeGenerators;
/// <inheritdoc />
public class PacketContextListAttributeGenerator : IParameterGenerator
{
+ /// <summary>
+ /// Gets the full name of the packet index attribute.
+ /// </summary>
+ public static string PacketListIndexAttributeFullName => "NosSmooth.Packets.Attributes.PacketContextListAttribute";
+
/// <inheritdoc />
- public bool ShouldHandle(AttributeSyntax attribute)
- => attribute.Name.NormalizeWhitespace().ToFullString() == "PacketContextList";
+ public bool ShouldHandle(ParameterInfo parameter)
+ => parameter.Attributes.Any(x => x.FullName == PacketListIndexAttributeFullName);
/// <inheritdoc />
- public IError? GenerateSerializerPart(IndentedTextWriter textWriter, RecordDeclarationSyntax recordDeclarationSyntax, ParameterInfo parameterInfo)
+ public IError? GenerateSerializerPart(IndentedTextWriter textWriter, PacketInfo packetInfo)
{
- if (parameterInfo.NamedAttributeArguments.ContainsKey("AfterSeparator") && parameterInfo.NamedAttributeArguments["AfterSeparator"] is not null)
- {
- textWriter.WriteLine($"builder.SetAfterSeparatorOnce('{parameterInfo.NamedAttributeArguments["AfterSeparator"]}');");
- }
+ var generator = new ConverterSerializationGenerator(textWriter);
+ var parameter = packetInfo.Parameters.Current;
+ var attribute = parameter.Attributes.First(x => x.FullName == PacketListIndexAttributeFullName);
- var listSeparator = '|';
- if (parameterInfo.NamedAttributeArguments.ContainsKey("ListSeparator") && parameterInfo.NamedAttributeArguments["ListSeparator"] is not null)
+ var afterSeparator = attribute.GetNamedValue<char?>("AfterSeparator", null);
+ if (afterSeparator is not null)
{
- listSeparator = parameterInfo.NamedAttributeArguments["ListSeparator"]!.ToString()[0];
+ generator.SetAfterSeparatorOnce((char)afterSeparator);
}
- textWriter.WriteLine($"builder.PushLevel('{listSeparator}')");
-
- var innerSeparator = '.';
- if (parameterInfo.NamedAttributeArguments.ContainsKey("InnerSeparator") && parameterInfo.NamedAttributeArguments["InnerSeparator"] is not null)
- {
- innerSeparator = parameterInfo.NamedAttributeArguments["InnerSeparator"]!.ToString()[0];
- textWriter.WriteLine($"builder.PushLevel('{parameterInfo.NamedAttributeArguments["InnerSeparator"]}');");
- }
+ var listSeparator = attribute.GetNamedValue<char>("ListSeparator", '|');
+ generator.PushLevel(listSeparator);
- textWriter.WriteLine($"builder.PrepareLevel('{innerSeparator}')");
+ var innerSeparator = attribute.GetNamedValue<char>("InnerSeparator", '.');
+ generator.PrepareLevel(innerSeparator);
- textWriter.WriteLine($@"
-var {parameterInfo.Name}Result = _typeConverterRepository.Serialize(obj.{parameterInfo.Name}, builder);
-if (!{parameterInfo.Name}Result.IsSuccess)
-{{
- return Result.FromError(new PacketParameterSerializerError(this, ""{parameterInfo.Name}"", {parameterInfo.Name}Result), {parameterInfo.Name}Result);
-}}
-
-builder.RemovePreparedLevel();
-builder.PopLevel();
-");
+ generator.SerializeAndCheck(parameter);
+ generator.RemovePreparedLevel();
+ generator.PopLevel();
return null;
}
/// <inheritdoc />
- public IError? GenerateDeserializerPart(IndentedTextWriter textWriter, RecordDeclarationSyntax recordDeclarationSyntax, ParameterInfo parameterInfo)
+ public IError? GenerateDeserializerPart(IndentedTextWriter textWriter, PacketInfo packetInfo)
{
- bool nullable = parameterInfo.Parameter.Type is NullableTypeSyntax;
+ var generator = new ConverterDeserializationGenerator(textWriter);
+ var parameter = packetInfo.Parameters.Current;
+ var attribute = parameter.Attributes.First(x => x.FullName == PacketListIndexAttributeFullName);
- if (parameterInfo.NamedAttributeArguments.ContainsKey("AfterSeparator") && parameterInfo.NamedAttributeArguments["AfterSeparator"] is not null)
+ var afterSeparator = attribute.GetNamedValue<char?>("AfterSeparator", null);
+ if (afterSeparator is not null)
{
- textWriter.WriteLine($"stringEnumerator.SetAfterSeparatorOnce('{parameterInfo.NamedAttributeArguments["AfterSeparator"]}');");
+ generator.SetAfterSeparatorOnce((char)afterSeparator);
}
- var listSeparator = '|';
- if (parameterInfo.NamedAttributeArguments.ContainsKey("ListSeparator") && parameterInfo.NamedAttributeArguments["ListSeparator"] is not null)
- {
- listSeparator = parameterInfo.NamedAttributeArguments["ListSeparator"]!.ToString()[0];
- }
+ var listSeparator = attribute.GetNamedValue<char>("ListSeparator", '|');
+ var lengthVariable = attribute.GetIndexedValue<string>(1);
+ textWriter.WriteLine(@$"stringEnumerator.PushLevel(""{listSeparator}"", {lengthVariable});");
- textWriter.WriteLine($"stringEnumerator.PushLevel('{listSeparator}');");
+ var innerSeparator = attribute.GetNamedValue<char>("InnerSeparator", '.');
+ generator.PrepareLevel(innerSeparator);
- var innerSeparator = '.';
- if (parameterInfo.NamedAttributeArguments.ContainsKey("InnerSeparator") && parameterInfo.NamedAttributeArguments["InnerSeparator"] is not null)
+ generator.DeserializeAndCheck($"{packetInfo.Namespace}.{packetInfo.Name}", parameter, packetInfo.Parameters.IsLast);
+ generator.RemovePreparedLevel();
+ generator.PopLevel();
+ if (!parameter.Nullable)
{
- innerSeparator = parameterInfo.NamedAttributeArguments["InnerSeparator"]!.ToString()[0];
+ generator.CheckNullError(parameter.GetResultVariableName(), parameter.Name);
}
- var maxTokensVariable = parameterInfo.IndexedAttributeArguments[1]!.ToString();
-
- textWriter.WriteLine($"stringEnumerator.PrepareLevel('{innerSeparator}', {maxTokensVariable});");
-
- var semanticModel = parameterInfo.Compilation.GetSemanticModel(recordDeclarationSyntax.SyntaxTree);
- var type = semanticModel.GetTypeInfo(parameterInfo.Parameter.Type!).Type;
- string last = parameterInfo.IsLast ? "true" : "false";
- textWriter.WriteLine($@"
-var {parameterInfo.Name}Result = _typeConverterRepository.Deserialize<{type!.ToString()}{(nullable ? string.Empty : "?")}>(stringEnumerator);
-var {parameterInfo.Name}Error = CheckDeserializationResult({parameterInfo.Name}Result, ""{parameterInfo.Name}"", stringEnumerator, {last});
-if ({parameterInfo.Name}Error is not null)
-{{
- return Result<{recordDeclarationSyntax.Identifier.NormalizeWhitespace().ToFullString()}?>.FromError({parameterInfo.Name}Error, {parameterInfo.Name}Result);
-}}
-
-stringEnumerator.RemovePreparedLevel();
-stringEnumerator.PopLevel();
-");
- if (!nullable)
- {
- textWriter.WriteLine($@"
-if ({parameterInfo.Name}Result.Entity is null) {{
- return new PacketParameterSerializerError(this, ""{parameterInfo.Name}"", {parameterInfo.Name}Result, ""The converter has returned null even though it was not expected."");
-}}
-");
- }
+ generator.AssignLocalVariable(parameter);
- textWriter.WriteLine($"var {parameterInfo.Name} = ({type.ToString()}){parameterInfo.Name}Result.Entity;");
return null;
}
}=
\ No newline at end of file
M Core/NosSmooth.PacketSerializersGenerator/AttributeGenerators/PacketIndexAttributeGenerator.cs => Core/NosSmooth.PacketSerializersGenerator/AttributeGenerators/PacketIndexAttributeGenerator.cs +38 -55
@@ 5,106 5,89 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System.CodeDom.Compiler;
-using Microsoft.CodeAnalysis;
-using Microsoft.CodeAnalysis.CSharp;
-using Microsoft.CodeAnalysis.CSharp.Syntax;
+using NosSmooth.PacketSerializersGenerator.Data;
using NosSmooth.PacketSerializersGenerator.Errors;
+using NosSmooth.PacketSerializersGenerator.Extensions;
namespace NosSmooth.PacketSerializersGenerator.AttributeGenerators;
/// <inheritdoc />
public class PacketIndexAttributeGenerator : IParameterGenerator
{
+ /// <summary>
+ /// Gets the full name of the packet index attribute.
+ /// </summary>
+ public static string PacketIndexAttributeFullName => "NosSmooth.Packets.Attributes.PacketIndexAttribute";
+
/// <inheritdoc />
- public bool ShouldHandle(AttributeSyntax attribute)
- => attribute.Name.NormalizeWhitespace().ToFullString() == "PacketIndex";
+ public bool ShouldHandle(ParameterInfo parameter)
+ => parameter.Attributes.Any(x => x.FullName == PacketIndexAttributeFullName);
/// <inheritdoc />
- public IError? GenerateSerializerPart(IndentedTextWriter textWriter, RecordDeclarationSyntax recordDeclarationSyntax, ParameterInfo parameterInfo)
+ public IError? GenerateSerializerPart(IndentedTextWriter textWriter, PacketInfo packetInfo)
{
bool pushedLevel = false;
+ var generator = new ConverterSerializationGenerator(textWriter);
+ var parameter = packetInfo.Parameters.Current;
+ var attribute = parameter.Attributes.First(x => x.FullName == PacketIndexAttributeFullName);
- if (parameterInfo.NamedAttributeArguments.ContainsKey("AfterSeparator") && parameterInfo.NamedAttributeArguments["AfterSeparator"] is not null)
+ var afterSeparator = attribute.GetNamedValue<char?>("AfterSeparator", null);
+ if (afterSeparator is not null)
{
- textWriter.WriteLine($"builder.SetAfterSeparatorOnce('{parameterInfo.NamedAttributeArguments["AfterSeparator"]}');");
+ generator.SetAfterSeparatorOnce((char)afterSeparator);
}
- if (parameterInfo.NamedAttributeArguments.ContainsKey("InnerSeparator") && parameterInfo.NamedAttributeArguments["InnerSeparator"] is not null)
+ var innerSeparator = attribute.GetNamedValue<char?>("InnerSeparator", null);
+ if (innerSeparator is not null)
{
+ generator.PushLevel((char)innerSeparator);
pushedLevel = true;
- textWriter.WriteLine($"builder.PushLevel('{parameterInfo.NamedAttributeArguments["InnerSeparator"]}');");
}
- var semanticModel = parameterInfo.Compilation.GetSemanticModel(recordDeclarationSyntax.SyntaxTree);
- var type = semanticModel.GetTypeInfo(parameterInfo.Parameter.Type!).Type;
- textWriter.WriteLine($@"
-var {parameterInfo.Name}Result = _typeConverterRepository.Serialize<{type}>(obj.{parameterInfo.Name}, builder);
-if (!{parameterInfo.Name}Result.IsSuccess)
-{{
- return Result.FromError(new PacketParameterSerializerError(this, ""{parameterInfo.Name}"", {parameterInfo.Name}Result), {parameterInfo.Name}Result);
-}}
-");
+ generator.SerializeAndCheck(parameter);
if (pushedLevel)
{
- textWriter.WriteLine("builder.PopLevel();");
+ generator.PopLevel();
}
return null;
}
/// <inheritdoc />
- public IError? GenerateDeserializerPart(IndentedTextWriter textWriter, RecordDeclarationSyntax recordDeclarationSyntax, ParameterInfo parameterInfo)
+ public IError? GenerateDeserializerPart(IndentedTextWriter textWriter, PacketInfo packetInfo)
{
bool pushedLevel = false;
- bool nullable = parameterInfo.Parameter.Type is NullableTypeSyntax;
+ var generator = new ConverterDeserializationGenerator(textWriter);
+ var parameter = packetInfo.Parameters.Current;
+ var attribute = parameter.Attributes.First();
- if (parameterInfo.NamedAttributeArguments.ContainsKey("AfterSeparator") && parameterInfo.NamedAttributeArguments["AfterSeparator"] is not null)
+ var afterSeparator = attribute.GetNamedValue<char?>("AfterSeparator", null);
+ if (afterSeparator is not null)
{
- textWriter.WriteLine($"stringEnumerator.SetAfterSeparatorOnce('{parameterInfo.NamedAttributeArguments["AfterSeparator"]}');");
+ generator.SetAfterSeparatorOnce((char)afterSeparator);
}
- if (parameterInfo.NamedAttributeArguments.ContainsKey("InnerSeparator") && parameterInfo.NamedAttributeArguments["InnerSeparator"] is not null)
+ var innerSeparator = attribute.GetNamedValue<char?>("InnerSeparator", null);
+ if (innerSeparator is not null)
{
+ generator.PushLevel((char)innerSeparator);
pushedLevel = true;
- textWriter.WriteLine($"stringEnumerator.PushLevel('{parameterInfo.NamedAttributeArguments["InnerSeparator"]}');");
}
- var semanticModel = parameterInfo.Compilation.GetSemanticModel(recordDeclarationSyntax.SyntaxTree);
- var type = semanticModel.GetTypeInfo(parameterInfo.Parameter.Type!).Type;
- string last = parameterInfo.IsLast ? "true" : "false";
- textWriter.WriteLine($@"
-var {parameterInfo.Name}Result = _typeConverterRepository.Deserialize<{type!.ToString().TrimEnd('?')}?>(stringEnumerator);
-var {parameterInfo.Name}Error = CheckDeserializationResult({parameterInfo.Name}Result, ""{parameterInfo.Name}"", stringEnumerator, {last});
-if ({parameterInfo.Name}Error is not null)
-{{
- return Result<{recordDeclarationSyntax.Identifier.NormalizeWhitespace().ToFullString()}?>.FromError({parameterInfo.Name}Error, {parameterInfo.Name}Result);
-}}
-");
-
- if (!nullable)
+ generator.DeserializeAndCheck($"{packetInfo.Namespace}.{packetInfo.Name}", parameter, packetInfo.Parameters.IsLast);
+
+ if (!parameter.Nullable)
{
- textWriter.WriteLine($@"
-if ({parameterInfo.Name}Result.Entity is null) {{
- return new PacketParameterSerializerError(this, ""{parameterInfo.Name}"", {parameterInfo.Name}Result, ""The converter has returned null even though it was not expected."");
-}}
-");
+ generator.CheckNullError(parameter.GetResultVariableName(), parameter.Name);
}
- textWriter.WriteLine($"var {parameterInfo.Name} = ({type!.ToString().TrimEnd('?')}{(nullable ? "?" : string.Empty)}){parameterInfo.Name}Result.Entity;");
+ generator.AssignLocalVariable(parameter);
if (pushedLevel)
{
- // If we know that we are not on the last token in the item level, just skip to the end of the item.
- // Note that if this is the case, then that means the converter is either corrupted
- // or the packet has more fields.
- textWriter.WriteLine($@"
-while (stringEnumerator.IsOnLastToken() == false)
-{{
- stringEnumerator.GetNextToken();
-}}
-");
- textWriter.WriteLine("stringEnumerator.PopLevel();");
+ generator.ReadToLastToken();
+ generator.PopLevel();
}
return null;
M Core/NosSmooth.PacketSerializersGenerator/AttributeGenerators/PacketListIndexAttributeGenerator.cs => Core/NosSmooth.PacketSerializersGenerator/AttributeGenerators/PacketListIndexAttributeGenerator.cs +42 -74
@@ 9,112 9,80 @@ using System.Reflection;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
+using NosSmooth.PacketSerializersGenerator.Data;
using NosSmooth.PacketSerializersGenerator.Errors;
+using NosSmooth.PacketSerializersGenerator.Extensions;
+using ParameterInfo = NosSmooth.PacketSerializersGenerator.Data.ParameterInfo;
namespace NosSmooth.PacketSerializersGenerator.AttributeGenerators;
/// <inheritdoc />
public class PacketListIndexAttributeGenerator : IParameterGenerator
{
+ /// <summary>
+ /// Gets the full name of the packet index attribute.
+ /// </summary>
+ public static string PacketListIndexAttributeFullName => "NosSmooth.Packets.Attributes.PacketListIndex";
+
/// <inheritdoc />
- public bool ShouldHandle(AttributeSyntax attribute)
- => attribute.Name.NormalizeWhitespace().ToFullString() == "PacketListIndex";
+ public bool ShouldHandle(ParameterInfo parameter)
+ => parameter.Attributes.Any(x => x.FullName == PacketListIndexAttributeFullName);
/// <inheritdoc />
- public IError? GenerateSerializerPart(IndentedTextWriter textWriter, RecordDeclarationSyntax recordDeclarationSyntax, ParameterInfo parameterInfo)
+ public IError? GenerateSerializerPart(IndentedTextWriter textWriter, PacketInfo packetInfo)
{
- if (parameterInfo.NamedAttributeArguments.ContainsKey("AfterSeparator") && parameterInfo.NamedAttributeArguments["AfterSeparator"] is not null)
- {
- textWriter.WriteLine($"builder.SetAfterSeparatorOnce('{parameterInfo.NamedAttributeArguments["AfterSeparator"]}');");
- }
+ var generator = new ConverterSerializationGenerator(textWriter);
+ var parameter = packetInfo.Parameters.Current;
+ var attribute = parameter.Attributes.First(x => x.FullName == PacketListIndexAttributeFullName);
- var listSeparator = '|';
- if (parameterInfo.NamedAttributeArguments.ContainsKey("ListSeparator") && parameterInfo.NamedAttributeArguments["ListSeparator"] is not null)
+ var afterSeparator = attribute.GetNamedValue<char?>("AfterSeparator", null);
+ if (afterSeparator is not null)
{
- listSeparator = parameterInfo.NamedAttributeArguments["ListSeparator"]!.ToString()[0];
+ generator.SetAfterSeparatorOnce((char)afterSeparator);
}
- textWriter.WriteLine($"builder.PushLevel('{listSeparator}');");
+ var listSeparator = attribute.GetNamedValue<char>("ListSeparator", '|');
+ generator.PushLevel(listSeparator);
- var innerSeparator = '.';
- if (parameterInfo.NamedAttributeArguments.ContainsKey("InnerSeparator") && parameterInfo.NamedAttributeArguments["InnerSeparator"] is not null)
- {
- innerSeparator = parameterInfo.NamedAttributeArguments["InnerSeparator"]!.ToString()[0];
- }
+ var innerSeparator = attribute.GetNamedValue<char>("InnerSeparator", '.');
+ generator.PrepareLevel(innerSeparator);
- textWriter.WriteLine($"builder.PrepareLevel('{innerSeparator}');");
-
- textWriter.WriteLine($@"
-var {parameterInfo.Name}Result = _typeConverterRepository.Serialize(obj.{parameterInfo.Name}, builder);
-if (!{parameterInfo.Name}Result.IsSuccess)
-{{
- return Result.FromError(new PacketParameterSerializerError(this, ""{parameterInfo.Name}"", {parameterInfo.Name}Result), {parameterInfo.Name}Result);
-}}
-
-builder.RemovePreparedLevel();
-builder.PopLevel();
-");
+ generator.SerializeAndCheck(parameter);
+ generator.RemovePreparedLevel();
+ generator.PopLevel();
return null;
}
/// <inheritdoc />
- public IError? GenerateDeserializerPart(IndentedTextWriter textWriter, RecordDeclarationSyntax recordDeclarationSyntax, ParameterInfo parameterInfo)
+ public IError? GenerateDeserializerPart(IndentedTextWriter textWriter, PacketInfo packetInfo)
{
- bool nullable = parameterInfo.Parameter.Type is NullableTypeSyntax;
-
- if (parameterInfo.NamedAttributeArguments.ContainsKey("AfterSeparator") && parameterInfo.NamedAttributeArguments["AfterSeparator"] is not null)
- {
- textWriter.WriteLine($"stringEnumerator.SetAfterSeparatorOnce('{parameterInfo.NamedAttributeArguments["AfterSeparator"]}');");
- }
-
- var listSeparator = '|';
- if (parameterInfo.NamedAttributeArguments.ContainsKey("ListSeparator") && parameterInfo.NamedAttributeArguments["ListSeparator"] is not null)
- {
- listSeparator = parameterInfo.NamedAttributeArguments["ListSeparator"]!.ToString()[0];
- }
-
- textWriter.WriteLine($"stringEnumerator.PushLevel('{listSeparator}');");
+ var generator = new ConverterDeserializationGenerator(textWriter);
+ var parameter = packetInfo.Parameters.Current;
+ var attribute = parameter.Attributes.First(x => x.FullName == PacketListIndexAttributeFullName);
- var innerSeparator = '.';
- if (parameterInfo.NamedAttributeArguments.ContainsKey("InnerSeparator") && parameterInfo.NamedAttributeArguments["InnerSeparator"] is not null)
+ var afterSeparator = attribute.GetNamedValue<char?>("AfterSeparator", null);
+ if (afterSeparator is not null)
{
- innerSeparator = parameterInfo.NamedAttributeArguments["InnerSeparator"]!.ToString()[0];
+ generator.SetAfterSeparatorOnce((char)afterSeparator);
}
- var maxTokens = "null";
- if (parameterInfo.NamedAttributeArguments.ContainsKey("Length") && parameterInfo.NamedAttributeArguments["Length"] is not null)
- {
- maxTokens = parameterInfo.NamedAttributeArguments["Length"]!.ToString();
- }
-
- textWriter.WriteLine($"stringEnumerator.PrepareLevel('{innerSeparator}', {maxTokens ?? "null"});");
-
- var semanticModel = parameterInfo.Compilation.GetSemanticModel(recordDeclarationSyntax.SyntaxTree);
- var type = semanticModel.GetTypeInfo(parameterInfo.Parameter.Type!).Type;
- string last = parameterInfo.IsLast ? "true" : "false";
- textWriter.WriteLine($@"
-var {parameterInfo.Name}Result = _typeConverterRepository.Deserialize<{type!.ToString()}{(nullable ? string.Empty : "?")}>(stringEnumerator);
-var {parameterInfo.Name}Error = CheckDeserializationResult({parameterInfo.Name}Result, ""{parameterInfo.Name}"", stringEnumerator, {last});
-if ({parameterInfo.Name}Error is not null)
-{{
- return Result<{recordDeclarationSyntax.Identifier.NormalizeWhitespace().ToFullString()}?>.FromError({parameterInfo.Name}Error, {parameterInfo.Name}Result);
-}}
+ var listSeparator = attribute.GetNamedValue<char>("ListSeparator", '|');
+ var length = attribute.GetNamedValue<int>("Length", 0);
+ generator.PushLevel(listSeparator, length != 0 ? (uint?)length : (uint?)null);
+ var innerSeparator = attribute.GetNamedValue<char>("InnerSeparator", '.');
+ generator.PrepareLevel(innerSeparator);
-stringEnumerator.RemovePreparedLevel();
-stringEnumerator.PopLevel();
-");
- if (!nullable)
+ generator.DeserializeAndCheck($"{packetInfo.Namespace}.{packetInfo.Name}", parameter, packetInfo.Parameters.IsLast);
+ generator.RemovePreparedLevel();
+ generator.PopLevel();
+ if (!parameter.Nullable)
{
- textWriter.WriteLine($@"
-if ({parameterInfo.Name}Result.Entity is null) {{
- return new PacketParameterSerializerError(this, ""{parameterInfo.Name}"", {parameterInfo.Name}Result, ""The converter has returned null even though it was not expected."");
-}}
-");
+ generator.CheckNullError(parameter.GetResultVariableName(), parameter.Name);
}
- textWriter.WriteLine($"var {parameterInfo.Name} = ({type.ToString()}){parameterInfo.Name}Result.Entity;");
+ generator.AssignLocalVariable(parameter);
return null;
}
A Core/NosSmooth.PacketSerializersGenerator/Constants.cs => Core/NosSmooth.PacketSerializersGenerator/Constants.cs +23 -0
@@ 0,0 1,23 @@
+//
+// Constants.cs
+//
+// Copyright (c) František Boháček. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace NosSmooth.PacketSerializersGenerator;
+
+/// <summary>
+/// Contains constants needed for the generation.
+/// </summary>
+public class Constants
+{
+ /// <summary>
+ /// Gets the full name of the generate source attribute class.
+ /// </summary>
+ public static string GenerateSourceAttributeClass => "NosSmooth.Packets.Attributes.GenerateSerializerAttribute";
+
+ /// <summary>
+ /// Gets the full name of the packet attribute classes that are used for the generation.
+ /// </summary>
+ public static string PacketAttributesClassRegex => @"^NosSmooth\.Packets\.Attributes\.Packet.*";
+}<
\ No newline at end of file
A Core/NosSmooth.PacketSerializersGenerator/ConverterDeserializationGenerator.cs => Core/NosSmooth.PacketSerializersGenerator/ConverterDeserializationGenerator.cs +135 -0
@@ 0,0 1,135 @@
+//
+// ConverterDeserializationGenerator.cs
+//
+// Copyright (c) František Boháček. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.CodeDom.Compiler;
+using NosSmooth.PacketSerializersGenerator.Data;
+using NosSmooth.PacketSerializersGenerator.Extensions;
+
+namespace NosSmooth.PacketSerializersGenerator;
+
+/// <summary>
+/// Various templates for converter deserialization.
+/// </summary>
+public class ConverterDeserializationGenerator
+{
+ private readonly string _stringEnumeratorVariable = "stringEnumerator";
+ private readonly IndentedTextWriter _textWriter;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ConverterDeserializationGenerator"/> class.
+ /// </summary>
+ /// <param name="textWriter">The text writer.</param>
+ public ConverterDeserializationGenerator(IndentedTextWriter textWriter)
+ {
+ _textWriter = textWriter;
+ }
+
+ /// <summary>
+ /// Push level to the string enumerator.
+ /// </summary>
+ /// <param name="separator">The separator.</param>
+ public void SetAfterSeparatorOnce(char separator)
+ {
+ _textWriter.WriteLine(@$"{_stringEnumeratorVariable}.SetAfterSeparatorOnce(""{separator}"");");
+ }
+
+ /// <summary>
+ /// Push level to the string enumerator.
+ /// </summary>
+ /// <param name="separator">The separator.</param>
+ /// <param name="maxTokens">The maximum number of tokens to read.</param>
+ public void PushLevel(char separator, uint? maxTokens = default)
+ {
+ _textWriter.WriteLine(@$"{_stringEnumeratorVariable}.PushLevel(""{separator}"", {maxTokens?.ToString() ?? "null"});");
+ }
+
+ /// <summary>
+ /// Pop level from the string enumerator.
+ /// </summary>
+ public void PopLevel()
+ {
+ _textWriter.WriteLine($"{_stringEnumeratorVariable}.PopLevel();");
+ }
+
+ /// <summary>
+ /// Prepare the level to the string enumerator.
+ /// </summary>
+ /// <param name="separator">The separator.</param>
+ /// <param name="maxTokens">The maximum number of tokens to read.</param>
+ public void PrepareLevel(char separator, uint? maxTokens = default)
+ {
+ _textWriter.WriteLine($@"{_stringEnumeratorVariable}.PrepareLevel(""{separator}"", {maxTokens?.ToString() ?? "null"});");
+ }
+
+ /// <summary>
+ /// Prepare the level to the string enumerator.
+ /// </summary>
+ public void RemovePreparedLevel()
+ {
+ _textWriter.WriteLine($@"{_stringEnumeratorVariable}.RemovePreparedLevel();");
+ }
+
+ /// <summary>
+ /// Try to read to the last token of the level.
+ /// </summary>
+ /// <remarks>
+ /// If we know that we are not on the last token in the item level, just skip to the end of the item.
+ /// Note that if this is the case, then that means the converter is either corrupted
+ /// or the packet has more fields.
+ /// </remarks>
+ public void ReadToLastToken()
+ {
+ _textWriter.WriteLine($@"while ({_stringEnumeratorVariable}.IsOnLastToken() == false)");
+ _textWriter.WriteLine("{");
+ _textWriter.Indent++;
+ _textWriter.WriteLine($"{_stringEnumeratorVariable}.GetNextToken();");
+ _textWriter.Indent--;
+ _textWriter.WriteLine("}");
+ }
+
+ /// <summary>
+ /// Deserialize the given parameter and check the result.
+ /// </summary>
+ /// <param name="packetFullName">The full name of the packet.</param>
+ /// <param name="parameter">The parameter to deserialize.</param>
+ /// <param name="isLast">Whether the token is the last one.</param>
+ public void DeserializeAndCheck(string packetFullName, ParameterInfo parameter, bool isLast)
+ {
+ string isLastString = isLast ? "true" : "false";
+ _textWriter.WriteMultiline($@"
+var {parameter.GetResultVariableName()} = _typeConverterRepository.Deserialize<{parameter.GetNullableType()}>({_stringEnumeratorVariable});
+var {parameter.GetErrorVariableName()} = CheckDeserializationResult({parameter.GetResultVariableName()}, ""{parameter.Name}"", {_stringEnumeratorVariable}, {isLastString});
+if ({parameter.GetErrorVariableName()} is not null)
+{{
+ return Result<{packetFullName}?>.FromError({parameter.GetErrorVariableName()}, {parameter.GetResultVariableName()});
+}}
+");
+ }
+
+ /// <summary>
+ /// Check taht the given variable is not null, if it is, return an error.
+ /// </summary>
+ /// <param name="resultVariableName">The result variable to check for null.</param>
+ /// <param name="parameterName">The parameter that is being parsed.</param>
+ /// <param name="reason">The reason for the error.</param>
+ public void CheckNullError(string resultVariableName, string parameterName, string reason = "The converter has returned null even though it was not expected.")
+ {
+ _textWriter.WriteMultiline($@"
+if ({resultVariableName}.Entity is null) {{
+ return new PacketParameterSerializerError(this, ""{parameterName}"", {resultVariableName}, ""{reason}"");
+}}
+");
+ }
+
+ /// <summary>
+ /// Assign local variable with the result of the parameter deserialization.
+ /// </summary>
+ /// <param name="parameter">The parameter.</param>
+ public void AssignLocalVariable(ParameterInfo parameter)
+ {
+ _textWriter.WriteLine($"var {parameter.Name} = ({parameter.GetActualType()}){parameter.GetResultVariableName()}.Entity;");
+ }
+}<
\ No newline at end of file
A Core/NosSmooth.PacketSerializersGenerator/ConverterSerializationGenerator.cs => Core/NosSmooth.PacketSerializersGenerator/ConverterSerializationGenerator.cs +93 -0
@@ 0,0 1,93 @@
+//
+// ConverterSerializationGenerator.cs
+//
+// Copyright (c) František Boháček. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.CodeDom.Compiler;
+using NosSmooth.PacketSerializersGenerator.Data;
+using NosSmooth.PacketSerializersGenerator.Extensions;
+
+namespace NosSmooth.PacketSerializersGenerator;
+
+/// <summary>
+/// Various templates for converter serialization.
+/// </summary>
+public class ConverterSerializationGenerator
+{
+ private readonly string _builderVariable = "builder";
+ private readonly IndentedTextWriter _textWriter;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ConverterSerializationGenerator"/> class.
+ /// </summary>
+ /// <param name="textWriter">The text writer.</param>
+ public ConverterSerializationGenerator(IndentedTextWriter textWriter)
+ {
+ _textWriter = textWriter;
+ }
+
+ /// <summary>
+ /// Push level to the string enumerator.
+ /// </summary>
+ /// <param name="separator">The separator.</param>
+ public void SetAfterSeparatorOnce(char separator)
+ {
+ _textWriter.WriteLine(@$"{_builderVariable}.SetAfterSeparatorOnce(""{separator}"");");
+ }
+
+ /// <summary>
+ /// Push level to the string enumerator.
+ /// </summary>
+ /// <param name="separator">The separator.</param>
+ public void PushLevel(char separator)
+ {
+ _textWriter.WriteLine
+ (@$"{_builderVariable}.PushLevel(""{separator}"");");
+ }
+
+ /// <summary>
+ /// Pop level from the string enumerator.
+ /// </summary>
+ public void PopLevel()
+ {
+ _textWriter.WriteLine($"{_builderVariable}.PopLevel();");
+ }
+
+ /// <summary>
+ /// Prepare the level to the string enumerator.
+ /// </summary>
+ /// <param name="separator">The separator.</param>
+ /// <param name="maxTokens">The maximum number of tokens to read.</param>
+ public void PrepareLevel(char separator, uint? maxTokens = default)
+ {
+ _textWriter.WriteLine
+ ($@"{_builderVariable}.PrepareLevel(""{separator}"", {maxTokens?.ToString() ?? "null"});");
+ }
+
+ /// <summary>
+ /// Prepare the level to the string enumerator.
+ /// </summary>
+ public void RemovePreparedLevel()
+ {
+ _textWriter.WriteLine($@"{_builderVariable}.RemovePreparedLevel();");
+ }
+
+ /// <summary>
+ /// Deserialize the given parameter and check the result.
+ /// </summary>
+ /// <param name="parameter">The parameter to deserialize.</param>
+ public void SerializeAndCheck(ParameterInfo parameter)
+ {
+ _textWriter.WriteMultiline
+ (
+ $@"
+var {parameter.GetResultVariableName()} = _typeConverterRepository.Serialize<{parameter.GetActualType()}>(obj.{parameter.Name}, {_builderVariable});
+if (!{parameter.GetResultVariableName()}.IsSuccess)
+{{
+ return Result.FromError(new PacketParameterSerializerError(this, ""{parameter.Name}"", {parameter.GetResultVariableName()}), {parameter.GetResultVariableName()});
+}}
+"
+ );
+ }
+}<
\ No newline at end of file
A Core/NosSmooth.PacketSerializersGenerator/Data/AttributeInfo.cs => Core/NosSmooth.PacketSerializersGenerator/Data/AttributeInfo.cs +24 -0
@@ 0,0 1,24 @@
+//
+// AttributeInfo.cs
+//
+// Copyright (c) František Boháček. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+namespace NosSmooth.PacketSerializersGenerator.Data;
+
+/// <summary>
+/// The attribute info.
+/// </summary>
+/// <param name="Attribute">The attribute syntax.</param>
+/// <param name="FullName">The full name of the attribute containing namespace.</param>
+/// <param name="IndexedAttributeArguments">The indexed arguments passed to the attribute.</param>
+/// <param name="NamedAttributeArguments">The named arguments passed to the attribute.</param>
+public record AttributeInfo
+(
+ AttributeSyntax Attribute,
+ string FullName,
+ IReadOnlyList<object?> IndexedAttributeArguments,
+ IReadOnlyDictionary<string, object?> NamedAttributeArguments
+);<
\ No newline at end of file
A Core/NosSmooth.PacketSerializersGenerator/Data/PacketInfo.cs => Core/NosSmooth.PacketSerializersGenerator/Data/PacketInfo.cs +29 -0
@@ 0,0 1,29 @@
+//
+// PacketInfo.cs
+//
+// Copyright (c) František Boháček. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+namespace NosSmooth.PacketSerializersGenerator.Data;
+
+/// <summary>
+/// Contains information about a packet record syntax.
+/// </summary>
+/// <param name="Compilation">The compilation of the generator.</param>
+/// <param name="PacketRecord">The packet record declaration.</param>
+/// <param name="SemanticModel">The semantic model the packet is in.</param>
+/// <param name="Parameters">The parsed parameters of the packet.</param>
+/// <param name="Namespace">The namespace of the packet record.</param>
+/// <param name="Name">The name of the packet.</param>
+public record PacketInfo
+(
+ Compilation Compilation,
+ RecordDeclarationSyntax PacketRecord,
+ SemanticModel SemanticModel,
+ Parameters Parameters,
+ string Namespace,
+ string Name
+);<
\ No newline at end of file
R Core/NosSmooth.PacketSerializersGenerator/ParameterInfo.cs => Core/NosSmooth.PacketSerializersGenerator/Data/ParameterInfo.cs +9 -17
@@ 7,33 7,25 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
-namespace NosSmooth.PacketSerializersGenerator;
+namespace NosSmooth.PacketSerializersGenerator.Data;
/// <summary>
/// Information about a parameter of a packet constructor.
/// </summary>
-/// <param name="Compilation"></param>
-/// <param name="Parameter"></param>
-/// <param name="Attribute"></param>
-/// <param name="IndexedAttributeArguments"></param>
-/// <param name="NamedAttributeArguments"></param>
+/// <param name="Parameter">The parameter's syntax.</param>
+/// <param name="Type">The type of the parameter.</param>
+/// <param name="Nullable">Whether the parameter type is nullable.</param>
+/// <param name="Attributes">The list of all of the attribute on the parameter that are used for the generation of serializers.</param>
/// <param name="Name"></param>
/// <param name="ConstructorIndex"></param>
/// <param name="PacketIndex"></param>
public record ParameterInfo
(
- Compilation Compilation,
ParameterSyntax Parameter,
- AttributeSyntax Attribute,
- IReadOnlyList<object?> IndexedAttributeArguments,
- IReadOnlyDictionary<string, object?> NamedAttributeArguments,
+ ITypeSymbol Type,
+ bool Nullable,
+ IReadOnlyList<AttributeInfo> Attributes,
string Name,
int ConstructorIndex,
int PacketIndex
-)
-{
- /// <summary>
- /// Gets or sets if this parameter is the last one.
- /// </summary>
- public bool IsLast { get; set; }
-}>
\ No newline at end of file
+);<
\ No newline at end of file
A Core/NosSmooth.PacketSerializersGenerator/Data/Parameters.cs => Core/NosSmooth.PacketSerializersGenerator/Data/Parameters.cs +57 -0
@@ 0,0 1,57 @@
+//
+// Parameters.cs
+//
+// Copyright (c) František Boháček. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace NosSmooth.PacketSerializersGenerator.Data;
+
+/// <summary>
+/// Contains set of parameters of a packet.
+/// </summary>
+public class Parameters
+{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="Parameters"/> class.
+ /// </summary>
+ /// <param name="parameters">The list of the parameters.</param>
+ public Parameters(IReadOnlyList<ParameterInfo> parameters)
+ {
+ List = parameters;
+ }
+
+ /// <summary>
+ /// Gets the list of the parameters.
+ /// </summary>
+ public IReadOnlyList<ParameterInfo> List { get; }
+
+ /// <summary>
+ /// Gets the current index of the parameter.
+ /// </summary>
+ public int CurrentIndex { get; set; }
+
+ /// <summary>
+ /// Gets the currently processing parameter.
+ /// </summary>
+ public ParameterInfo Current => List[CurrentIndex];
+
+ /// <summary>
+ /// Gets the next processing parameter.
+ /// </summary>
+ public ParameterInfo Next => List[CurrentIndex];
+
+ /// <summary>
+ /// Gets the previous processing parameter.
+ /// </summary>
+ public ParameterInfo Previous => List[CurrentIndex];
+
+ /// <summary>
+ /// Gets whether the current parameter is the last one.
+ /// </summary>
+ public bool IsLast => CurrentIndex == List.Count - 1;
+
+ /// <summary>
+ /// Gets whether the current parameter is the first one.
+ /// </summary>
+ public bool IsFirst => CurrentIndex == 0;
+}<
\ No newline at end of file
A Core/NosSmooth.PacketSerializersGenerator/Extensions/AttributeArgumentSyntaxExtensions.cs => Core/NosSmooth.PacketSerializersGenerator/Extensions/AttributeArgumentSyntaxExtensions.cs +33 -0
@@ 0,0 1,33 @@
+//
+// AttributeArgumentSyntaxExtensions.cs
+//
+// Copyright (c) František Boháček. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+namespace NosSmooth.PacketSerializersGenerator.Extensions;
+
+/// <summary>
+/// Extension methods for <see cref="AttributeArgumentSyntax"/>.
+/// </summary>
+public static class AttributeArgumentSyntaxExtensions
+{
+ /// <summary>
+ /// Get the value of the argument.
+ /// </summary>
+ /// <param name="attributeArgument">The attribute argument.</param>
+ /// <param name="semanticModel">The semantic model containing the attribute argument info.</param>
+ /// <returns>The value.</returns>
+ public static object? GetValue(this AttributeArgumentSyntax attributeArgument, SemanticModel semanticModel)
+ {
+ var value = semanticModel.GetConstantValue(attributeArgument.Expression);
+ if (!value.HasValue)
+ {
+ return null;
+ }
+
+ return value;
+ }
+}<
\ No newline at end of file
A Core/NosSmooth.PacketSerializersGenerator/Extensions/AttributeInfoExtensions.cs => Core/NosSmooth.PacketSerializersGenerator/Extensions/AttributeInfoExtensions.cs +58 -0
@@ 0,0 1,58 @@
+//
+// AttributeInfoExtensions.cs
+//
+// Copyright (c) František Boháček. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Diagnostics;
+using NosSmooth.PacketSerializersGenerator.Data;
+
+namespace NosSmooth.PacketSerializersGenerator.Extensions;
+
+/// <summary>
+/// Extension methods for <see cref="AttributeInfo"/>.
+/// </summary>
+public static class AttributeInfoExtensions
+{
+ /// <summary>
+ /// Get value of a named parameter.
+ /// </summary>
+ /// <param name="attributeInfo">The attribute information.</param>
+ /// <param name="name">The name of the parameter.</param>
+ /// <param name="default">The default value to return if not found.</param>
+ /// <typeparam name="TValue">The value type.</typeparam>
+ /// <returns>The value of the attribute.</returns>
+ public static TValue? GetNamedValue<TValue>(this AttributeInfo attributeInfo, string name, TValue? @default)
+ {
+ if (attributeInfo.NamedAttributeArguments.TryGetValue(name, out var value))
+ {
+ if (typeof(TValue) == typeof(string))
+ {
+ return (TValue?)(object?)value?.ToString();
+ }
+
+ return (TValue?)value;
+ }
+
+ return @default;
+ }
+
+ /// <summary>
+ /// Get value of a named parameter.
+ /// </summary>
+ /// <param name="attributeInfo">The attribute information.</param>
+ /// <param name="index">The index of the parameter.</param>
+ /// <typeparam name="TValue">The value type.</typeparam>
+ /// <returns>The value of the attribute.</returns>
+ public static TValue? GetIndexedValue<TValue>(this AttributeInfo attributeInfo, int index)
+ {
+ var value = attributeInfo.IndexedAttributeArguments[index];
+
+ if (typeof(TValue) == typeof(string))
+ {
+ return (TValue?)(object?)value?.ToString();
+ }
+
+ return (TValue?)value;
+ }
+}<
\ No newline at end of file
A Core/NosSmooth.PacketSerializersGenerator/Extensions/AttributeListSyntaxExtensions.cs => Core/NosSmooth.PacketSerializersGenerator/Extensions/AttributeListSyntaxExtensions.cs +29 -0
@@ 0,0 1,29 @@
+//
+// AttributeListSyntaxExtensions.cs
+//
+// Copyright (c) František Boháček. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Text.RegularExpressions;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+namespace NosSmooth.PacketSerializersGenerator.Extensions;
+
+/// <summary>
+/// Extension methods for <see cref="AttributeListSyntax"/>.
+/// </summary>
+public static class AttributeListSyntaxExtensions
+{
+ /// <summary>
+ /// Whether the attribute list contains the attribute with the given full name.
+ /// </summary>
+ /// <param name="attributeList">The list of the attributes.</param>
+ /// <param name="semanticModel">The semantic model.</param>
+ /// <param name="attributeFullName">The full name of the attribute.</param>
+ /// <returns>Whether the attribute is present.</returns>
+ public static bool ContainsAttribute(this AttributeListSyntax attributeList, SemanticModel semanticModel, string attributeFullName)
+ {
+ return attributeList.Attributes.Any(x => Regex.IsMatch(attributeFullName, semanticModel.GetTypeInfo(x).Type?.ToString()!));
+ }
+}<
\ No newline at end of file
A Core/NosSmooth.PacketSerializersGenerator/Extensions/IndentedTextWriterExtensions.cs => Core/NosSmooth.PacketSerializersGenerator/Extensions/IndentedTextWriterExtensions.cs +28 -0
@@ 0,0 1,28 @@
+//
+// IndentedTextWriterExtensions.cs
+//
+// Copyright (c) František Boháček. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.CodeDom.Compiler;
+
+namespace NosSmooth.PacketSerializersGenerator.Extensions;
+
+/// <summary>
+/// Extension methods for <see cref="IndentedTextWriter"/>.
+/// </summary>
+public static class IndentedTextWriterExtensions
+{
+ /// <summary>
+ /// Append multiline text with correct indentation.
+ /// </summary>
+ /// <param name="textWriter">The text writer to write to.</param>
+ /// <param name="text">The text to write.</param>
+ public static void WriteMultiline(this IndentedTextWriter textWriter, string text)
+ {
+ foreach (var line in text.Split('\n'))
+ {
+ textWriter.WriteLine(line);
+ }
+ }
+}<
\ No newline at end of file
A Core/NosSmooth.PacketSerializersGenerator/Extensions/ParameterInfoExtensions.cs => Core/NosSmooth.PacketSerializersGenerator/Extensions/ParameterInfoExtensions.cs +79 -0
@@ 0,0 1,79 @@
+//
+// ParameterInfoExtensions.cs
+//
+// Copyright (c) František Boháček. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using NosSmooth.PacketSerializersGenerator.Data;
+using NosSmooth.PacketSerializersGenerator.Errors;
+
+namespace NosSmooth.PacketSerializersGenerator.Extensions;
+
+/// <summary>
+/// Extensions for <see cref="ParameterInfo"/>.
+/// </summary>
+public static class ParameterInfoExtensions
+{
+ /// <summary>
+ /// Gets the name of the error variable.
+ /// </summary>
+ /// <param name="parameterInfo">The parameter.</param>
+ /// <returns>The name of the error variable.</returns>
+ public static string GetErrorVariableName(this ParameterInfo parameterInfo)
+ {
+ return $"{parameterInfo.Name}Error";
+ }
+
+ /// <summary>
+ /// Gets the name of the error variable.
+ /// </summary>
+ /// <param name="parameterInfo">The parameter.</param>
+ /// <returns>The name of the error variable.</returns>
+ public static string GetResultVariableName(this ParameterInfo parameterInfo)
+ {
+ return $"{parameterInfo.Name}Result";
+ }
+
+ /// <summary>
+ /// Gets the name of the error variable.
+ /// </summary>
+ /// <param name="parameterInfo">The parameter.</param>
+ /// <returns>The name of the error variable.</returns>
+ public static string GetVariableName(this ParameterInfo parameterInfo)
+ {
+ return parameterInfo.Name;
+ }
+
+ /// <summary>
+ /// Gets the type of the parameter as nullable.
+ /// </summary>
+ /// <param name="parameterInfo">The parameter.</param>
+ /// <returns>The nullable type.</returns>
+ public static string GetNullableType(this ParameterInfo parameterInfo)
+ {
+ return parameterInfo.Type.ToString().TrimEnd('?') + "?";
+ }
+
+ /// <summary>
+ /// Gets the type of the parameter with ? if the parameter is nullable..
+ /// </summary>
+ /// <param name="parameterInfo">The parameter.</param>
+ /// <returns>The type.</returns>
+ public static string GetActualType(this ParameterInfo parameterInfo)
+ {
+ return parameterInfo.Type.ToString().TrimEnd('?') + (parameterInfo.Nullable ? "?" : string.Empty);
+ }
+
+ /// <summary>
+ /// Gets whether the parameter is marked as optional.
+ /// </summary>
+ /// <param name="parameterInfo">The parameter info.</param>
+ /// <returns>Whether the parameter is optional.</returns>
+ public static bool IsOptional(this ParameterInfo parameterInfo)
+ {
+ return parameterInfo.Attributes.First().GetNamedValue("IsOptional", false);
+ }
+}<
\ No newline at end of file
D Core/NosSmooth.PacketSerializersGenerator/PacketClassReceiver.cs => Core/NosSmooth.PacketSerializersGenerator/PacketClassReceiver.cs +0 -50
@@ 1,50 0,0 @@
-//
-// PacketClassReceiver.cs
-//
-// Copyright (c) František Boháček. All rights reserved.
-// Licensed under the MIT license. See LICENSE file in the project root for full license information.
-
-using Microsoft.CodeAnalysis;
-using Microsoft.CodeAnalysis.CSharp;
-using Microsoft.CodeAnalysis.CSharp.Syntax;
-
-namespace NosSmooth.PacketSerializersGenerator
-{
- /// <summary>
- /// Syntax receiver of classes with generate attribute.
- /// </summary>
- public class PacketClassReceiver : ISyntaxReceiver
- {
- private readonly List<RecordDeclarationSyntax> _packetClasses;
-
- /// <summary>
- /// Gets the name of the attribute that indicates the packet should have a serializer generated.
- /// </summary>
- public static string AttributeFullName => "GenerateSerializer";
-
- /// <summary>
- /// Initializes a new instance of the <see cref="PacketClassReceiver"/> class.
- /// </summary>
- public PacketClassReceiver()
- {
- _packetClasses = new List<RecordDeclarationSyntax>();
- }
-
- /// <summary>
- /// Gets the classes that should have serializers generated.
- /// </summary>
- public IReadOnlyList<RecordDeclarationSyntax> PacketClasses => _packetClasses;
-
- /// <inheritdoc />
- public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
- {
- if (syntaxNode.IsKind(SyntaxKind.RecordDeclaration) && syntaxNode is RecordDeclarationSyntax classDeclarationSyntax)
- {
- if (classDeclarationSyntax.AttributeLists.Any(x => x.Attributes.Any(x => x.Name.NormalizeWhitespace().ToFullString() == AttributeFullName)))
- {
- _packetClasses.Add(classDeclarationSyntax);
- }
- }
- }
- }
-}>
\ No newline at end of file
A Core/NosSmooth.PacketSerializersGenerator/PacketConverterGenerator.cs => Core/NosSmooth.PacketSerializersGenerator/PacketConverterGenerator.cs +245 -0
@@ 0,0 1,245 @@
+//
+// PacketConverterGenerator.cs
+//
+// Copyright (c) František Boháček. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.CodeDom.Compiler;
+using NosSmooth.PacketSerializersGenerator.AttributeGenerators;
+using NosSmooth.PacketSerializersGenerator.Data;
+using NosSmooth.PacketSerializersGenerator.Errors;
+using NosSmooth.PacketSerializersGenerator.Extensions;
+
+namespace NosSmooth.PacketSerializersGenerator;
+
+/// <summary>
+/// Code generator of a packet converter.
+/// </summary>
+public class PacketConverterGenerator
+{
+ private readonly PacketInfo _packetInfo;
+ private readonly IReadOnlyList<IParameterGenerator> _parameterGenerators;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="PacketConverterGenerator"/> class.
+ /// </summary>
+ /// <param name="packetInfo">The packet type information.</param>
+ /// <param name="parameterGenerators">The converter parameter generators.</param>
+ public PacketConverterGenerator(PacketInfo packetInfo, IReadOnlyList<IParameterGenerator> parameterGenerators)
+ {
+ _packetInfo = packetInfo;
+ _parameterGenerators = parameterGenerators;
+ }
+
+ /// <summary>
+ /// Generate the converter class.
+ /// </summary>
+ /// <param name="textWriter">The text writer to write the class to.</param>
+ /// <returns>An error, if any.</returns>
+ public IError? Generate(IndentedTextWriter textWriter)
+ {
+ textWriter.WriteLine
+ (
+ @$"// <auto-generated/>
+#nullable enable
+#pragma warning disable 1591
+
+using {_packetInfo.Namespace};
+using NosSmooth.Packets.Converters;
+using NosSmooth.Packets.Errors;
+using NosSmooth.Packets;
+using Remora.Results;
+
+namespace {_packetInfo.Namespace}.Generated;
+
+public class {_packetInfo.Name}Converter : BaseTypeConverter<{_packetInfo.Name}>
+{{"
+ );
+ textWriter.Indent++;
+ textWriter.WriteLine
+ (
+ $@"
+private readonly ITypeConverterRepository _typeConverterRepository;
+
+public {_packetInfo.Name}Converter(ITypeConverterRepository typeConverterRepository)
+{{
+ _typeConverterRepository = typeConverterRepository;
+}}
+
+/// <inheritdoc />
+public override Result Serialize({_packetInfo.Name}? obj, PacketStringBuilder builder)
+{{
+ if (obj is null)
+ {{
+ return new ArgumentNullError(nameof(obj));
+ }}
+"
+ );
+ textWriter.Indent++;
+ var serializerError = GenerateSerializer(textWriter);
+ if (serializerError is not null)
+ {
+ return serializerError;
+ }
+
+ textWriter.Indent--;
+ textWriter.WriteLine
+ (
+ $@"
+}}
+
+/// <inheritdoc />
+public override Result<{_packetInfo.Name}?> Deserialize(PacketStringEnumerator stringEnumerator)
+{{
+"
+ );
+
+ textWriter.Indent++;
+ var deserializerError = GenerateDeserializer(textWriter);
+ if (deserializerError is not null)
+ {
+ return deserializerError;
+ }
+ textWriter.Indent--;
+
+ textWriter.WriteLine
+ (
+ $@"
+ }}
+
+private IResultError? CheckDeserializationResult<T>(Result<T> result, string property, PacketStringEnumerator stringEnumerator, bool last = false)
+{{
+ if (!result.IsSuccess)
+ {{
+ return new PacketParameterSerializerError(this, property, result);
+ }}
+
+ if (!last && (stringEnumerator.IsOnLastToken() ?? false))
+ {{
+ return new PacketEndNotExpectedError(this, property);
+ }}
+
+ return null;
+}}
+}}"
+ );
+ return null;
+ }
+
+ private IError? GenerateSerializer(IndentedTextWriter textWriter)
+ {
+ _packetInfo.Parameters.CurrentIndex = 0;
+ foreach (var parameter in _packetInfo.Parameters.List)
+ {
+ bool handled = false;
+ foreach (var generator in _parameterGenerators)
+ {
+ if (generator.ShouldHandle(parameter))
+ {
+ var result = generator.GenerateSerializerPart(textWriter, _packetInfo);
+ if (result is not null)
+ {
+ return result;
+ }
+
+ handled = true;
+ break;
+ }
+ }
+
+ if (!handled)
+ {
+ throw new InvalidOperationException
+ (
+ $"Could not handle {_packetInfo.Namespace}.{_packetInfo.Name}.{parameter.Name}"
+ );
+ }
+ _packetInfo.Parameters.CurrentIndex++;
+ }
+
+ textWriter.WriteLine("return Result.FromSuccess();");
+ return null;
+ }
+
+ private IError? GenerateDeserializer
+ (IndentedTextWriter textWriter)
+ {
+ _packetInfo.Parameters.CurrentIndex = 0;
+ var lastIndex = _packetInfo.Parameters.Current.PacketIndex - 1;
+ bool skipped = false;
+ foreach (var parameter in _packetInfo.Parameters.List)
+ {
+ var skip = parameter.PacketIndex - lastIndex - 1;
+ if (skip > 0)
+ {
+ if (!skipped)
+ {
+ textWriter.WriteLine("Result<PacketToken> skipResult;");
+ textWriter.WriteLine("IResultError? skipError;");
+ skipped = true;
+ }
+ textWriter.WriteLine($@"skipResult = stringEnumerator.GetNextToken();");
+ textWriter.WriteLine
+ ("skipError = CheckDeserializationResult(result, \"None\", stringEnumerator, false);");
+ textWriter.WriteMultiline
+ (
+ $@"if (skipError is not null) {{
+ return Result<{_packetInfo.Name}>.FromError(skipError, skipResult);
+}}"
+ );
+ }
+ else if (skip < 0)
+ {
+ return new DiagnosticError
+ (
+ "SG0004",
+ "Same packet index",
+ "There were two parameters of the same packet index {0} on property {1} in packet {2}, that is not supported.",
+ parameter.Attributes.First().Attribute.SyntaxTree,
+ parameter.Attributes.First().Attribute.FullSpan,
+ new List<object?>
+ (
+ new[]
+ {
+ parameter.PacketIndex.ToString(),
+ parameter.Name,
+ _packetInfo.Name
+ }
+ )
+ );
+ }
+
+ bool handled = false;
+ foreach (var generator in _parameterGenerators)
+ {
+ if (generator.ShouldHandle(parameter))
+ {
+ var result = generator.GenerateDeserializerPart(textWriter, _packetInfo);
+ if (result is not null)
+ {
+ return result;
+ }
+
+ handled = true;
+ break;
+ }
+ }
+
+ if (!handled)
+ {
+ throw new InvalidOperationException
+ (
+ $"Could not handle {_packetInfo.Name}.{parameter.Name}"
+ );
+ }
+ lastIndex = parameter.PacketIndex;
+ _packetInfo.Parameters.CurrentIndex++;
+ }
+
+ string parametersString = string.Join(", ", _packetInfo.Parameters.List.OrderBy(x => x.ConstructorIndex).Select(x => x.GetVariableName()));
+ textWriter.WriteLine
+ ($"return new {_packetInfo.Name}({parametersString});");
+
+ return null;
+ }
+}<
\ No newline at end of file
R Core/NosSmooth.PacketSerializersGenerator/PacketSerializerGenerator.cs => Core/NosSmooth.PacketSerializersGenerator/SourceGenerator.cs +146 -243
@@ 1,16 1,16 @@
//
-// PacketSerializerGenerator.cs
+// SourceGenerator.cs
//
// Copyright (c) František Boháček. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System.CodeDom.Compiler;
using System.Diagnostics;
-using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using NosSmooth.PacketSerializersGenerator.AttributeGenerators;
+using NosSmooth.PacketSerializersGenerator.Data;
using NosSmooth.PacketSerializersGenerator.Errors;
using NosSmooth.PacketSerializersGenerator.Extensions;
@@ 23,19 23,22 @@ namespace NosSmooth.PacketSerializersGenerator;
/// The packets to create serializer for have to be records that specify PacketIndices in the constructor.
/// </remarks>
[Generator]
-public class PacketSerializerGenerator : ISourceGenerator
+public class SourceGenerator : ISourceGenerator
{
/// <summary>
- /// Initializes a new instance of the <see cref="PacketSerializerGenerator"/> class.
+ /// Initializes a new instance of the <see cref="SourceGenerator"/> class.
/// </summary>
- public PacketSerializerGenerator()
+ public SourceGenerator()
{
- _generators = new List<IParameterGenerator>(new IParameterGenerator[]
- {
- new PacketIndexAttributeGenerator(),
- new PacketListIndexAttributeGenerator(),
- new PacketContextListAttributeGenerator(),
- });
+ _generators = new List<IParameterGenerator>
+ (
+ new IParameterGenerator[]
+ {
+ new PacketIndexAttributeGenerator(),
+ new PacketListIndexAttributeGenerator(),
+ new PacketContextListAttributeGenerator(),
+ }
+ );
}
private readonly List<IParameterGenerator> _generators;
@@ 43,26 46,44 @@ public class PacketSerializerGenerator : ISourceGenerator
/// <inheritdoc />
public void Initialize(GeneratorInitializationContext context)
{
- context.RegisterForSyntaxNotifications(() => new PacketClassReceiver());
+ SpinWait.SpinUntil(() => Debugger.IsAttached);
+ }
+
+ private IEnumerable<RecordDeclarationSyntax> GetPacketRecords(Compilation compilation, SyntaxTree tree)
+ {
+ var semanticModel = compilation.GetSemanticModel(tree);
+ var root = tree.GetRoot();
+
+ return root
+ .DescendantNodes()
+ .OfType<RecordDeclarationSyntax>()
+ .Where
+ (
+ x => x.AttributeLists.Any
+ (y => y.ContainsAttribute(semanticModel, Constants.GenerateSourceAttributeClass))
+ );
}
/// <inheritdoc />
public void Execute(GeneratorExecutionContext context)
{
- var syntaxReceiver = (PacketClassReceiver)context.SyntaxReceiver!;
+ var packetRecords = context.Compilation.SyntaxTrees
+ .SelectMany(x => GetPacketRecords(context.Compilation, x));
- foreach (var packetClass in syntaxReceiver.PacketClasses)
+ foreach (var packetRecord in packetRecords)
{
- if (packetClass is not null)
+ if (packetRecord is not null)
{
using var stringWriter = new StringWriter();
using var writer = new IndentedTextWriter(stringWriter, " ");
- var generatedResult = GeneratePacketSerializer(writer, context.Compilation, packetClass);
+ var generatedResult = GeneratePacketSerializer(writer, context.Compilation, packetRecord);
if (generatedResult is not null)
{
if (generatedResult is DiagnosticError diagnosticError)
{
- context.ReportDiagnostic(Diagnostic.Create
+ context.ReportDiagnostic
+ (
+ Diagnostic.Create
(
new DiagnosticDescriptor
(
@@ 86,12 107,17 @@ public class PacketSerializerGenerator : ISourceGenerator
continue;
}
- context.AddSource($"{packetClass.Identifier.NormalizeWhitespace().ToFullString()}Converter.g.cs", stringWriter.GetStringBuilder().ToString());
+ context.AddSource
+ (
+ $"{packetRecord.Identifier.NormalizeWhitespace().ToFullString()}Converter.g.cs",
+ stringWriter.GetStringBuilder().ToString()
+ );
}
}
}
- private IError? GeneratePacketSerializer(IndentedTextWriter textWriter, Compilation compilation, RecordDeclarationSyntax packetClass)
+ private IError? GeneratePacketSerializer
+ (IndentedTextWriter textWriter, Compilation compilation, RecordDeclarationSyntax packetClass)
{
var semanticModel = compilation.GetSemanticModel(packetClass.SyntaxTree);
@@ 103,16 129,20 @@ public class PacketSerializerGenerator : ISourceGenerator
if (constructor is null)
{
- return new DiagnosticError(
+ return new DiagnosticError
+ (
"SG0001",
"Packet without constructor",
"The packet class {0} does not have any constructors to use for packet serializer.",
packetClass.SyntaxTree,
packetClass.FullSpan,
- new List<object?>(new[]
- {
- packetClass.Identifier.NormalizeWhitespace().ToFullString()
- })
+ new List<object?>
+ (
+ new[]
+ {
+ packetClass.Identifier.NormalizeWhitespace().ToFullString()
+ }
+ )
);
}
@@ 121,255 151,128 @@ public class PacketSerializerGenerator : ISourceGenerator
int constructorIndex = 0;
foreach (var parameter in parameters)
{
- var attributeLists = parameter.AttributeLists
- .Where(x => x.Attributes.Any(x => x.Name.NormalizeWhitespace().ToFullString().StartsWith("Packet"))).ToList();
- var attributes = attributeLists.FirstOrDefault()?.Attributes.Where(x => x.Name.NormalizeWhitespace().ToFullString().StartsWith("Packet"))
- .ToList();
+ var createError = CreateParameterInfo
+ (
+ packetClass,
+ parameter,
+ semanticModel,
+ constructorIndex,
+ out var parameterInfo
+ );
- if (attributeLists.Count > 1 || (attributes is not null && attributes?.Count > 1))
- {
- return new DiagnosticError
- (
- "SG0002",
- "Packet constructor parameter with multiple packet attributes",
- "There are multiple PacketIndexAttributes on {0} parameter in class {1}. Only one may be specified.",
- parameter.SyntaxTree,
- parameter.FullSpan,
- new List<object?>(new[]
- {
- parameter.Identifier.NormalizeWhitespace().ToFullString(),
- name
- })
- );
- }
- else if (attributeLists.Count == 0 || attributes is null || attributes.Count < 1)
+ if (createError is not null)
{
- return new DiagnosticError(
- "SG0003",
- "Packet constructor parameter without packet attribute",
- "Could not find PacketIndexAttribute on {0} parameter in class {1}. Parameters without PacketIndexAttribute aren't allowed.",
- parameter.SyntaxTree,
- parameter.FullSpan,
- new List<object?>(new[]
- {
- parameter.Identifier.NormalizeWhitespace().ToFullString(),
- name
- })
- );
+ return createError;
}
- var attribute = attributes.First();
- var indexArg = attribute.ArgumentList!.Arguments[0];
- var indexExp = indexArg.Expression;
- var index = ushort.Parse(semanticModel.GetConstantValue(indexExp).ToString());
- var namedArguments = new Dictionary<string, object?>();
- var arguments = new List<object?>();
-
- foreach (var argument in attribute.ArgumentList.Arguments)
+ if (parameterInfo is not null)
{
- var argumentName = argument.NameEquals?.Name.Identifier.NormalizeWhitespace().ToFullString();
- var expression = argument.Expression;
- var value = semanticModel.GetConstantValue(expression).Value;
-
- if (argumentName is not null)
- {
- namedArguments.Add(argumentName, value);
- }
- else
- {
- arguments.Add(value);
- }
+ orderedParameters.Add(parameterInfo);
}
- orderedParameters.Add(new ParameterInfo
- (
- compilation,
- parameter,
- attribute,
- arguments,
- namedArguments,
- parameter.Identifier.NormalizeWhitespace().ToFullString(),
- constructorIndex,
- index
- )
- );
constructorIndex++;
}
orderedParameters = orderedParameters.OrderBy(x => x.PacketIndex).ToList();
- orderedParameters.Last().IsLast = true;
-
- textWriter.WriteLine(@$"// <auto-generated/>
-#nullable enable
-#pragma warning disable 1591
-
-using {@namespace};
-using NosSmooth.Packets.Converters;
-using NosSmooth.Packets.Errors;
-using NosSmooth.Packets;
-using Remora.Results;
-
-namespace {@namespace}.Generated;
-
-public class {name}Converter : BaseTypeConverter<{name}>
-{{");
- textWriter.Indent++;
- textWriter.WriteLine($@"
-private readonly ITypeConverterRepository _typeConverterRepository;
-
-public {name}Converter(ITypeConverterRepository typeConverterRepository)
-{{
- _typeConverterRepository = typeConverterRepository;
-}}
-
-/// <inheritdoc />
-public override Result Serialize({name}? obj, PacketStringBuilder builder)
-{{
- if (obj is null)
- {{
- return new ArgumentNullError(nameof(obj));
- }}
-");
- textWriter.Indent++;
- var serializerError = GenerateSerializer(textWriter, packetClass, orderedParameters);
- if (serializerError is not null)
- {
- return serializerError;
- }
-
- textWriter.Indent--;
- textWriter.WriteLine($@"
-}}
-
-/// <inheritdoc />
-public override Result<{name}?> Deserialize(PacketStringEnumerator stringEnumerator)
-{{
-");
-
- textWriter.Indent++;
- var deserializerError = GenerateDeserializer(textWriter, packetClass, orderedParameters);
- if (deserializerError is not null)
- {
- return deserializerError;
- }
- textWriter.Indent--;
-
- textWriter.WriteLine($@"
- }}
-
-private IResultError? CheckDeserializationResult<T>(Result<T> result, string property, PacketStringEnumerator stringEnumerator, bool last = false)
-{{
- if (!result.IsSuccess)
- {{
- return new PacketParameterSerializerError(this, property, result);
- }}
-
- if (!last && (stringEnumerator.IsOnLastToken() ?? false))
- {{
- return new PacketEndNotExpectedError(this, property);
- }}
-
- return null;
-}}
-}}");
- return null;
+ var packetInfo = new PacketInfo
+ (
+ compilation,
+ packetClass,
+ semanticModel,
+ new Parameters(orderedParameters),
+ @namespace,
+ name
+ );
+
+ var generator = new PacketConverterGenerator(packetInfo, _generators);
+ var generatorError = generator.Generate(textWriter);
+
+ return generatorError;
}
- private IError? GenerateSerializer(IndentedTextWriter textWriter, RecordDeclarationSyntax packetClass, List<ParameterInfo> parameters)
+ private IError? CreateParameterInfo
+ (
+ RecordDeclarationSyntax packet,
+ ParameterSyntax parameter,
+ SemanticModel semanticModel,
+ int constructorIndex,
+ out ParameterInfo? parameterInfo
+ )
{
- foreach (var parameter in parameters)
+ var name = packet.Identifier.NormalizeWhitespace().ToFullString();
+
+ parameterInfo = null;
+ var attributes = parameter.AttributeLists
+ .Where(x => x.ContainsAttribute(semanticModel, Constants.PacketAttributesClassRegex))
+ .SelectMany(x => x.Attributes)
+ .ToList();
+
+ if (attributes.Count == 0)
{
- bool handled = false;
- foreach (var generator in _generators)
- {
- if (generator.ShouldHandle(parameter.Attribute))
- {
- var result = generator.GenerateSerializerPart(textWriter, packetClass, parameter);
- if (result is not null)
+ return new DiagnosticError
+ (
+ "SG0003",
+ "Packet constructor parameter without packet attribute",
+ "Could not find PacketIndexAttribute on {0} parameter in class {1}. Parameters without PacketIndexAttribute aren't allowed.",
+ parameter.SyntaxTree,
+ parameter.FullSpan,
+ new List<object?>
+ (
+ new[]
{
- return result;
+ parameter.Identifier.NormalizeWhitespace().ToFullString(),
+ name
}
-
- handled = true;
- break;
- }
- }
-
- if (!handled)
- {
- throw new InvalidOperationException($"Could not handle {packetClass.Identifier.NormalizeWhitespace().ToFullString()}.{parameter.Name}");
- }
+ )
+ );
}
- textWriter.WriteLine("return Result.FromSuccess();");
+ var attribute = attributes.First();
+ var index = ushort.Parse(attribute.ArgumentList!.Arguments[0].GetValue(semanticModel)!.ToString());
+
+ List<AttributeInfo> attributeInfos = attributes
+ .Select(x => CreateAttributeInfo(x, semanticModel))
+ .ToList();
+
+ parameterInfo = new ParameterInfo
+ (
+ parameter,
+ semanticModel.GetTypeInfo(parameter.Type!).Type!,
+ parameter.Type is NullableTypeSyntax,
+ attributeInfos,
+ parameter.Identifier.NormalizeWhitespace().ToFullString(),
+ constructorIndex,
+ index
+ );
return null;
}
- private IError? GenerateDeserializer(IndentedTextWriter textWriter, RecordDeclarationSyntax packetClass, List<ParameterInfo> parameters)
+ private AttributeInfo CreateAttributeInfo(AttributeSyntax attribute, SemanticModel semanticModel)
{
- var lastIndex = (parameters.FirstOrDefault()?.PacketIndex ?? 0) - 1;
- bool skipped = false;
- foreach (var parameter in parameters)
+ var namedArguments = new Dictionary<string, object?>();
+ var arguments = new List<object?>();
+
+ foreach (var argument in attribute.ArgumentList!.Arguments)
{
- var skip = parameter.PacketIndex - lastIndex - 1;
- if (skip > 0)
- {
- if (!skipped)
- {
- textWriter.WriteLine("Result<PacketToken> skipResult;");
- textWriter.WriteLine("IResultError? skipError;");
- skipped = true;
- }
- textWriter.WriteLine($@"skipResult = stringEnumerator.GetNextToken();");
- textWriter.WriteLine($@"skipError = CheckDeserializationResult(result, ""None"", stringEnumerator, false);");
- textWriter.WriteLine($@"if (skipError is not null) {{
- return Result<{packetClass.Identifier.NormalizeWhitespace().ToFullString()}>.FromError(skipError, skipResult);
-}}");
- }
- else if (skip < 0)
- {
- return new DiagnosticError
- (
- "SG0004",
- "Same packet index",
- "There were two parameters of the same packet index {0} on property {1} in packet {2}, that is not supported.",
- parameter.Attribute.SyntaxTree,
- parameter.Attribute.FullSpan,
- new List<object?>(new[]
- {
- parameter.PacketIndex.ToString(),
- parameter.Name,
- packetClass.Identifier.NormalizeWhitespace().ToFullString()
- })
- );
- }
+ var argumentName = argument.NameEquals?.Name.Identifier.NormalizeWhitespace().ToFullString();
+ var value = argument.GetValue(semanticModel);
- bool handled = false;
- foreach (var generator in _generators)
+ if (argumentName is not null)
{
- if (generator.ShouldHandle(parameter.Attribute))
- {
- var result = generator.GenerateDeserializerPart(textWriter, packetClass, parameter);
- if (result is not null)
- {
- return result;
- }
-
- handled = true;
- break;
- }
+ namedArguments.Add(argumentName, value);
}
-
- if (!handled)
+ else
{
- throw new InvalidOperationException($"Could not handle {packetClass.Identifier.NormalizeWhitespace().ToFullString()}.{parameter.Name}");
+ arguments.Add(value);
}
- lastIndex = parameter.PacketIndex;
}
- string parametersString = string.Join(", ", parameters.OrderBy(x => x.ConstructorIndex).Select(x => x.Name));
- textWriter.WriteLine($"return new {packetClass.Identifier.NormalizeWhitespace().ToFullString()}({parametersString});");
-
- return null;
+ return new AttributeInfo
+ (
+ attribute,
+ semanticModel.GetTypeInfo(attribute).Type?.ToString()!,
+ arguments,
+ namedArguments
+ );
}
}=
\ No newline at end of file