From 694395943dc490fee5b44da78f009a390ef1de3f Mon Sep 17 00:00:00 2001 From: Rutherther Date: Sun, 8 Jan 2023 12:31:03 +0100 Subject: [PATCH] feat(packets): add conditional list index attribute --- .../PacketConditionalListIndexAttribute.cs | 27 ++ ...tConditionalListIndexAttributeGenerator.cs | 242 ++++++++++++++++++ .../PacketListIndexAttributeGenerator.cs | 19 +- .../SourceGenerator.cs | 1 + 4 files changed, 286 insertions(+), 3 deletions(-) create mode 100644 Packets/NosSmooth.PacketSerializer.Abstractions/Attributes/PacketConditionalListIndexAttribute.cs create mode 100644 Packets/NosSmooth.PacketSerializersGenerator/AttributeGenerators/PacketConditionalListIndexAttributeGenerator.cs diff --git a/Packets/NosSmooth.PacketSerializer.Abstractions/Attributes/PacketConditionalListIndexAttribute.cs b/Packets/NosSmooth.PacketSerializer.Abstractions/Attributes/PacketConditionalListIndexAttribute.cs new file mode 100644 index 0000000..4b93e17 --- /dev/null +++ b/Packets/NosSmooth.PacketSerializer.Abstractions/Attributes/PacketConditionalListIndexAttribute.cs @@ -0,0 +1,27 @@ +// +// PacketConditionalListIndexAttribute.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.PacketSerializer.Abstractions.Attributes; + +/// +/// + +/// in one. +/// +public class PacketConditionalListIndexAttribute : PacketListIndexAttribute +{ + /// + /// Initializes a new instance of the class. + /// You can use this attribute multiple times on one parameter. + /// + /// The position in the packet. + /// What parameter to check. (it has to precede this one). + /// Whether to negate the match values (not equals). + /// The values that mean this parameter is present. + public PacketConditionalListIndexAttribute(ushort index, string conditionParameter, bool negate = false, params object?[] matchValues) + : base(index) + { + } +} \ No newline at end of file diff --git a/Packets/NosSmooth.PacketSerializersGenerator/AttributeGenerators/PacketConditionalListIndexAttributeGenerator.cs b/Packets/NosSmooth.PacketSerializersGenerator/AttributeGenerators/PacketConditionalListIndexAttributeGenerator.cs new file mode 100644 index 0000000..12d1856 --- /dev/null +++ b/Packets/NosSmooth.PacketSerializersGenerator/AttributeGenerators/PacketConditionalListIndexAttributeGenerator.cs @@ -0,0 +1,242 @@ +// +// PacketConditionalListIndexAttributeGenerator.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 Microsoft.CodeAnalysis.CSharp.Syntax; +using NosSmooth.PacketSerializersGenerator.Data; +using NosSmooth.PacketSerializersGenerator.Errors; +using NosSmooth.PacketSerializersGenerator.Extensions; + +namespace NosSmooth.PacketSerializersGenerator.AttributeGenerators; + +/// +public class PacketConditionalListIndexAttributeGenerator : IParameterGenerator +{ + private readonly PacketListIndexAttributeGenerator _listIndexGenerator; + private readonly InlineTypeConverterGenerator _inlineTypeConverterGenerators; + + /// + /// Initializes a new instance of the class. + /// + /// The generator for types. + public PacketConditionalListIndexAttributeGenerator(InlineTypeConverterGenerator inlineTypeConverterGenerators) + { + _inlineTypeConverterGenerators = inlineTypeConverterGenerators; + _listIndexGenerator = new PacketListIndexAttributeGenerator(inlineTypeConverterGenerators) + { + PacketListIndexAttributeFullName = PacketConditionalListIndexAttributeFullName + }; + } + + /// + /// Gets the full name of the packet index attribute. + /// + public static string PacketConditionalListIndexAttributeFullName + => "NosSmooth.PacketSerializer.Abstractions.Attributes.PacketConditionalListIndexAttribute"; + + /// + public bool ShouldHandle(ParameterInfo parameter) + => parameter.Attributes.Any(x => x.FullName == PacketConditionalListIndexAttributeFullName); + + /// + public IError? CheckParameter(PacketInfo packet, ParameterInfo parameter) + { + if (!parameter.Nullable) + { + return new DiagnosticError + ( + "SGNull", + "Conditional parameters must be nullable", + "The parameter {0} in {1} has to be nullable, because it is conditional.", + parameter.Parameter.SyntaxTree, + parameter.Parameter.FullSpan, + new List(new[] { parameter.Name, packet.Name }) + ); + } + + if (parameter.Attributes.Any(x => x.FullName != PacketConditionalListIndexAttributeFullName)) + { + return new DiagnosticError + ( + "SGAttr", + "Packet constructor parameter with multiple packet attributes", + "Found multiple packet attributes of multiple types on parameter {0} in {1}. PacketConditionalIndexAttribute supports multiple attributes of the same type only.", + parameter.Parameter.SyntaxTree, + parameter.Parameter.FullSpan, + new List + ( + new[] + { + parameter.Name, + packet.Name + } + ) + ); + } + + // Check that all attributes have the same data. (where the same data need to be) + var firstAttribute = parameter.Attributes.First(); + if (parameter.Attributes.Any + ( + x => + { + var index = x.GetIndexedValue(0); + if (index != parameter.PacketIndex) + { + return true; + } + + foreach (var keyValue in x.NamedAttributeArguments) + { + if (!firstAttribute.NamedAttributeArguments.ContainsKey(keyValue.Key)) + { + return true; + } + + if (firstAttribute.NamedAttributeArguments[keyValue.Key] != keyValue.Value) + { + return true; + } + } + + return false; + } + )) + { + return new DiagnosticError + ( + "SGAttr", + "Packet constructor parameter with multiple conflicting attribute data.", + "Found multiple packet attributes of multiple types on parameter {0} in {1} with conflicting data. Index, IsOptional, InnerSeparator, ListSeparator, AfterSeparator all have to be the same for each attribute.", + parameter.Parameter.SyntaxTree, + parameter.Parameter.FullSpan, + new List + ( + new[] + { + parameter.Name, + packet.Name + } + ) + ); + } + + var mismatchedAttribute = parameter.Attributes.FirstOrDefault + ( + x => x.IndexedAttributeArguments.Count < 4 || + (x.IndexedAttributeArguments[3].IsArray && + ( + (x.IndexedAttributeArguments[3].Argument.Expression as ArrayCreationExpressionSyntax) + ?.Initializer is null || + (x.IndexedAttributeArguments[3].Argument.Expression as ArrayCreationExpressionSyntax) + ?.Initializer?.Expressions.Count == 0 + ) + ) + ); + if (mismatchedAttribute is not null) + { + return new DiagnosticError + ( + "SGAttr", + "Packet conditional attribute without matching values", + "Found PacketConditionalIndexAttribute without matchValues parameters set on {0} in {1}. At least one parameter has to be specified.", + mismatchedAttribute.Attribute.SyntaxTree, + mismatchedAttribute.Attribute.FullSpan, + new List + ( + new[] + { + parameter.Name, + packet.Name + } + ) + ); + } + + return ParameterChecker.CheckOptionalIsNullable(packet, parameter); + } + + private string BuildAttributeIfPart(AttributeInfo attribute, string prefix) + { + var conditionParameterName = attribute.GetIndexedValue(1); + var negate = attribute.GetIndexedValue(2); + var values = attribute.GetParamsVisualValues(3); + if (conditionParameterName is null || values is null) + { + throw new ArgumentException(); + } + + var inner = string.Join + (" || ", values.Select(x => $"{prefix}{conditionParameterName.Trim('"')} == {x?.ToString() ?? "null"}")); + return (negate ? "!(" : string.Empty) + inner + (negate ? ")" : string.Empty); + } + + private string BuildParameterIfStatement(ParameterInfo parameter, string prefix) + { + var ifInside = string.Empty; + foreach (var attribute in parameter.Attributes) + { + ifInside += BuildAttributeIfPart(attribute, prefix); + } + + return $"if ({ifInside})"; + } + + /// + public IError? GenerateSerializerPart(IndentedTextWriter textWriter, PacketInfo packetInfo) + { + var parameter = packetInfo.Parameters.Current; + + // begin conditional if + textWriter.WriteLine(BuildParameterIfStatement(parameter, "obj.")); + textWriter.WriteLine("{"); + textWriter.Indent++; + + var error = _listIndexGenerator.GenerateSerializerPart(textWriter, packetInfo); + if (error is not null) + { + return error; + } + + // end conditional if + textWriter.Indent--; + textWriter.WriteLine("}"); + + return null; + } + + /// + public IError? GenerateDeserializerPart(IndentedTextWriter textWriter, PacketInfo packetInfo) + { + var generator = new ConverterDeserializationGenerator(textWriter); + var parameter = packetInfo.Parameters.Current; + + generator.DeclareLocalVariable(parameter); + + // begin conditional if + textWriter.WriteLine(BuildParameterIfStatement(parameter, string.Empty)); + textWriter.WriteLine("{"); + textWriter.Indent++; + + var error = _listIndexGenerator.GenerateDeserializerPart(textWriter, packetInfo, false); + if (error is not null) + { + return error; + } + + // end conditional if + textWriter.Indent--; + textWriter.WriteLine("}"); + textWriter.WriteLine("else"); + textWriter.WriteLine("{"); + textWriter.Indent++; + textWriter.WriteLine($"{parameter.GetVariableName()} = null;"); + textWriter.Indent--; + textWriter.WriteLine("}"); + + return null; + } +} \ No newline at end of file diff --git a/Packets/NosSmooth.PacketSerializersGenerator/AttributeGenerators/PacketListIndexAttributeGenerator.cs b/Packets/NosSmooth.PacketSerializersGenerator/AttributeGenerators/PacketListIndexAttributeGenerator.cs index 225e786..33f3d44 100644 --- a/Packets/NosSmooth.PacketSerializersGenerator/AttributeGenerators/PacketListIndexAttributeGenerator.cs +++ b/Packets/NosSmooth.PacketSerializersGenerator/AttributeGenerators/PacketListIndexAttributeGenerator.cs @@ -33,8 +33,8 @@ public class PacketListIndexAttributeGenerator : IParameterGenerator /// /// Gets the full name of the packet index attribute. /// - public static string PacketListIndexAttributeFullName - => "NosSmooth.PacketSerializer.Abstractions.Attributes.PacketListIndexAttribute"; + public string PacketListIndexAttributeFullName { get; set; } + = "NosSmooth.PacketSerializer.Abstractions.Attributes.PacketListIndexAttribute"; /// public bool ShouldHandle(ParameterInfo parameter) @@ -94,12 +94,25 @@ public class PacketListIndexAttributeGenerator : IParameterGenerator /// public IError? GenerateDeserializerPart(IndentedTextWriter textWriter, PacketInfo packetInfo) + => GenerateDeserializerPart(textWriter, packetInfo, true); + + /// + /// Generate part for the Deserializer method to deserialize the given parameter. + /// + /// The text writer to write the code to. + /// The packet info to generate for. + /// Whether to declare the local variable. + /// The generated source code. + public IError? GenerateDeserializerPart(IndentedTextWriter textWriter, PacketInfo packetInfo, bool declare) { var generator = new ConverterDeserializationGenerator(textWriter); var parameter = packetInfo.Parameters.Current; var attribute = parameter.Attributes.First(x => x.FullName == PacketListIndexAttributeFullName); - generator.DeclareLocalVariable(parameter); + if (declare) + { + generator.DeclareLocalVariable(parameter); + } // add optional if if (parameter.IsOptional()) diff --git a/Packets/NosSmooth.PacketSerializersGenerator/SourceGenerator.cs b/Packets/NosSmooth.PacketSerializersGenerator/SourceGenerator.cs index 3026003..d172d06 100644 --- a/Packets/NosSmooth.PacketSerializersGenerator/SourceGenerator.cs +++ b/Packets/NosSmooth.PacketSerializersGenerator/SourceGenerator.cs @@ -54,6 +54,7 @@ public class SourceGenerator : ISourceGenerator new PacketListIndexAttributeGenerator(inlineTypeConverter), new PacketContextListAttributeGenerator(inlineTypeConverter), new PacketConditionalIndexAttributeGenerator(inlineTypeConverter), + new PacketConditionalListIndexAttributeGenerator(inlineTypeConverter), } ); } -- 2.49.0