From ce9e26d5b40e21a9a4f9432f4881c2625b8e0ba4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franti=C5=A1ek=20Boh=C3=A1=C4=8Dek?= Date: Fri, 31 Dec 2021 10:14:24 +0100 Subject: [PATCH] feat: add support for optional parameters --- .../PacketContextListAttributeGenerator.cs | 30 ++++++++ .../PacketIndexAttributeGenerator.cs | 41 ++++++++++- .../PacketListIndexAttributeGenerator.cs | 34 ++++++++- .../ConverterDeserializationGenerator.cs | 69 ++++++++++++++++++- .../Attributes/PacketIndexAttribute.cs | 14 ++++ .../PacketStringEnumerator.cs | 15 +++- 6 files changed, 197 insertions(+), 6 deletions(-) diff --git a/Core/NosSmooth.PacketSerializersGenerator/AttributeGenerators/PacketContextListAttributeGenerator.cs b/Core/NosSmooth.PacketSerializersGenerator/AttributeGenerators/PacketContextListAttributeGenerator.cs index 59ed39d..831620c 100644 --- a/Core/NosSmooth.PacketSerializersGenerator/AttributeGenerators/PacketContextListAttributeGenerator.cs +++ b/Core/NosSmooth.PacketSerializersGenerator/AttributeGenerators/PacketContextListAttributeGenerator.cs @@ -33,6 +33,13 @@ public class PacketContextListAttributeGenerator : IParameterGenerator var parameter = packetInfo.Parameters.Current; var attribute = parameter.Attributes.First(x => x.FullName == PacketListIndexAttributeFullName); + if (parameter.IsOptional()) + { + textWriter.WriteLine($"if (obj.{parameter.GetVariableName()} is not null)"); + textWriter.WriteLine("{"); + textWriter.Indent++; + } + var afterSeparator = attribute.GetNamedValue("AfterSeparator", null); if (afterSeparator is not null) { @@ -49,6 +56,13 @@ public class PacketContextListAttributeGenerator : IParameterGenerator generator.RemovePreparedLevel(); generator.PopLevel(); + // end optional if + if (parameter.IsOptional()) + { + textWriter.Indent--; + textWriter.WriteLine("}"); + } + return null; } @@ -59,6 +73,16 @@ public class PacketContextListAttributeGenerator : IParameterGenerator var parameter = packetInfo.Parameters.Current; var attribute = parameter.Attributes.First(x => x.FullName == PacketListIndexAttributeFullName); + // add optional if + if (parameter.IsOptional()) + { + var error = generator.StartOptionalCheck(parameter, packetInfo.Name); + if (error is not null) + { + return error; + } + } + var afterSeparator = attribute.GetNamedValue("AfterSeparator", null); if (afterSeparator is not null) { @@ -82,6 +106,12 @@ public class PacketContextListAttributeGenerator : IParameterGenerator generator.AssignLocalVariable(parameter); + // end is last token if body + if (parameter.IsOptional()) + { + generator.EndOptionalCheck(parameter); + } + return null; } } \ No newline at end of file diff --git a/Core/NosSmooth.PacketSerializersGenerator/AttributeGenerators/PacketIndexAttributeGenerator.cs b/Core/NosSmooth.PacketSerializersGenerator/AttributeGenerators/PacketIndexAttributeGenerator.cs index 92003c8..985a24c 100644 --- a/Core/NosSmooth.PacketSerializersGenerator/AttributeGenerators/PacketIndexAttributeGenerator.cs +++ b/Core/NosSmooth.PacketSerializersGenerator/AttributeGenerators/PacketIndexAttributeGenerator.cs @@ -31,12 +31,21 @@ public class PacketIndexAttributeGenerator : IParameterGenerator var parameter = packetInfo.Parameters.Current; var attribute = parameter.Attributes.First(x => x.FullName == PacketIndexAttributeFullName); + if (parameter.IsOptional()) + { + textWriter.WriteLine($"if (obj.{parameter.GetVariableName()} is not null)"); + textWriter.WriteLine("{"); + textWriter.Indent++; + } + + // register after separator var afterSeparator = attribute.GetNamedValue("AfterSeparator", null); if (afterSeparator is not null) { generator.SetAfterSeparatorOnce((char)afterSeparator); } + // push inner separator level var innerSeparator = attribute.GetNamedValue("InnerSeparator", null); if (innerSeparator is not null) { @@ -44,13 +53,22 @@ public class PacketIndexAttributeGenerator : IParameterGenerator pushedLevel = true; } + // serialize, check the error. generator.SerializeAndCheck(parameter); + // pop inner separator level if (pushedLevel) { generator.PopLevel(); } + // end optional if + if (parameter.IsOptional()) + { + textWriter.Indent--; + textWriter.WriteLine("}"); + } + return null; } @@ -62,6 +80,18 @@ public class PacketIndexAttributeGenerator : IParameterGenerator var parameter = packetInfo.Parameters.Current; var attribute = parameter.Attributes.First(); + generator.DeclareLocalVariable(parameter); + + // add optional if + if (parameter.IsOptional()) + { + var error = generator.StartOptionalCheck(parameter, packetInfo.Name); + if (error is not null) + { + return error; + } + } + var afterSeparator = attribute.GetNamedValue("AfterSeparator", null); if (afterSeparator is not null) { @@ -75,14 +105,15 @@ public class PacketIndexAttributeGenerator : IParameterGenerator pushedLevel = true; } - generator.DeserializeAndCheck($"{packetInfo.Namespace}.{packetInfo.Name}", parameter, packetInfo.Parameters.IsLast); + generator.DeserializeAndCheck + ($"{packetInfo.Namespace}.{packetInfo.Name}", parameter, packetInfo.Parameters.IsLast); if (!parameter.Nullable) { generator.CheckNullError(parameter.GetResultVariableName(), parameter.Name); } - generator.AssignLocalVariable(parameter); + generator.AssignLocalVariable(parameter, false); if (pushedLevel) { @@ -90,6 +121,12 @@ public class PacketIndexAttributeGenerator : IParameterGenerator generator.PopLevel(); } + // end is last token if body + if (parameter.IsOptional()) + { + generator.EndOptionalCheck(parameter); + } + return null; } } \ No newline at end of file diff --git a/Core/NosSmooth.PacketSerializersGenerator/AttributeGenerators/PacketListIndexAttributeGenerator.cs b/Core/NosSmooth.PacketSerializersGenerator/AttributeGenerators/PacketListIndexAttributeGenerator.cs index 05197cd..7058e71 100644 --- a/Core/NosSmooth.PacketSerializersGenerator/AttributeGenerators/PacketListIndexAttributeGenerator.cs +++ b/Core/NosSmooth.PacketSerializersGenerator/AttributeGenerators/PacketListIndexAttributeGenerator.cs @@ -35,6 +35,13 @@ public class PacketListIndexAttributeGenerator : IParameterGenerator var parameter = packetInfo.Parameters.Current; var attribute = parameter.Attributes.First(x => x.FullName == PacketListIndexAttributeFullName); + if (parameter.IsOptional()) + { + textWriter.WriteLine($"if (obj.{parameter.GetVariableName()} is not null)"); + textWriter.WriteLine("{"); + textWriter.Indent++; + } + var afterSeparator = attribute.GetNamedValue("AfterSeparator", null); if (afterSeparator is not null) { @@ -51,6 +58,13 @@ public class PacketListIndexAttributeGenerator : IParameterGenerator generator.RemovePreparedLevel(); generator.PopLevel(); + // end optional if + if (parameter.IsOptional()) + { + textWriter.Indent--; + textWriter.WriteLine("}"); + } + return null; } @@ -61,6 +75,18 @@ public class PacketListIndexAttributeGenerator : IParameterGenerator var parameter = packetInfo.Parameters.Current; var attribute = parameter.Attributes.First(x => x.FullName == PacketListIndexAttributeFullName); + generator.DeclareLocalVariable(parameter); + + // add optional if + if (parameter.IsOptional()) + { + var error = generator.StartOptionalCheck(parameter, packetInfo.Name); + if (error is not null) + { + return error; + } + } + var afterSeparator = attribute.GetNamedValue("AfterSeparator", null); if (afterSeparator is not null) { @@ -82,7 +108,13 @@ public class PacketListIndexAttributeGenerator : IParameterGenerator generator.CheckNullError(parameter.GetResultVariableName(), parameter.Name); } - generator.AssignLocalVariable(parameter); + generator.AssignLocalVariable(parameter, false); + + // end is last token if body + if (parameter.IsOptional()) + { + generator.EndOptionalCheck(parameter); + } return null; } diff --git a/Core/NosSmooth.PacketSerializersGenerator/ConverterDeserializationGenerator.cs b/Core/NosSmooth.PacketSerializersGenerator/ConverterDeserializationGenerator.cs index 723b7b1..c22f858 100644 --- a/Core/NosSmooth.PacketSerializersGenerator/ConverterDeserializationGenerator.cs +++ b/Core/NosSmooth.PacketSerializersGenerator/ConverterDeserializationGenerator.cs @@ -6,6 +6,7 @@ using System.CodeDom.Compiler; using NosSmooth.PacketSerializersGenerator.Data; +using NosSmooth.PacketSerializersGenerator.Errors; using NosSmooth.PacketSerializersGenerator.Extensions; namespace NosSmooth.PacketSerializersGenerator; @@ -36,6 +37,14 @@ public class ConverterDeserializationGenerator _textWriter.WriteLine(@$"{_stringEnumeratorVariable}.SetAfterSeparatorOnce('{separator}');"); } + /// + /// Sets that the next token should be read to the last entry in the level. + /// + public void SetReadToLast() + { + _textWriter.WriteLine(@$"{_stringEnumeratorVariable}.SetReadToLast();"); + } + /// /// Push level to the string enumerator. /// @@ -128,8 +137,64 @@ if ({resultVariableName}.Entity is null) {{ /// Assign local variable with the result of the parameter deserialization. /// /// The parameter. - public void AssignLocalVariable(ParameterInfo parameter) + public void DeclareLocalVariable(ParameterInfo parameter) + { + _textWriter.WriteLine($"{parameter.GetActualType()} {parameter.GetVariableName()};"); + } + + /// + /// Assign local variable with the result of the parameter deserialization. + /// + /// The parameter. + /// Whether to also declare the local variable. + public void AssignLocalVariable(ParameterInfo parameter, bool declare = true) + { + _textWriter.WriteLine($"{(declare ? "var " : string.Empty)}{parameter.Name} = ({parameter.GetActualType()}){parameter.GetResultVariableName()}.Entity;"); + } + + /// + /// Begins the if for optionals, check if the parameter is not nullable. + /// + /// The parameter information. + /// The name of the packet. + /// An error, if any. + public IError? StartOptionalCheck(ParameterInfo parameter, string packetName) { - _textWriter.WriteLine($"var {parameter.Name} = ({parameter.GetActualType()}){parameter.GetResultVariableName()}.Entity;"); + if (!parameter.Nullable) + { + return new DiagnosticError + ( + "SG0006", + "Optional parameters must be nullable", + "The parameter {0} in {1} has to be nullable, because it is optional.", + parameter.Parameter.SyntaxTree, + parameter.Parameter.FullSpan, + new List(new[] { parameter.Name, packetName }) + ); + } + + // serialize this parameter only if we are not on the last token. + _textWriter.WriteLine($"if (!(stringEnumerator.IsOnLastToken() ?? true))"); + _textWriter.WriteLine("{"); + _textWriter.Indent++; + return null; + } + + /// + /// Ends the if for optionals. + /// + /// The parameter information. + public void EndOptionalCheck(ParameterInfo parameter) + { + _textWriter.Indent--; + _textWriter.WriteLine("}"); + _textWriter.WriteLine("else"); + _textWriter.WriteLine("{"); + _textWriter.Indent++; + + _textWriter.WriteLine($"{parameter.GetVariableName()} = null;"); + + _textWriter.Indent--; + _textWriter.WriteLine("}"); } } \ No newline at end of file diff --git a/Core/NosSmooth.Packets/Attributes/PacketIndexAttribute.cs b/Core/NosSmooth.Packets/Attributes/PacketIndexAttribute.cs index 80dcfb3..a75c788 100644 --- a/Core/NosSmooth.Packets/Attributes/PacketIndexAttribute.cs +++ b/Core/NosSmooth.Packets/Attributes/PacketIndexAttribute.cs @@ -37,4 +37,18 @@ public class PacketIndexAttribute : Attribute /// Gets the separator after this field. /// public char AfterSeparator { get; set; } = (char)0xFF; + + /// + /// Gets or sets whether this parameter is optional. + /// + /// + /// Optional attributes have to be nullable, + /// if the attribute is not in the string, + /// it will be set to null. For serializer, + /// if the parameter is null, it will be omitted. + /// + /// See for + /// more complex decision making about using parameters. + /// + public bool IsOptional { get; set; } = false; } \ No newline at end of file diff --git a/Core/NosSmooth.Packets/PacketStringEnumerator.cs b/Core/NosSmooth.Packets/PacketStringEnumerator.cs index 2407544..1b8fee3 100644 --- a/Core/NosSmooth.Packets/PacketStringEnumerator.cs +++ b/Core/NosSmooth.Packets/PacketStringEnumerator.cs @@ -21,6 +21,7 @@ public struct PacketStringEnumerator private EnumeratorLevel _currentLevel; private (char Separator, uint? MaxTokens)? _preparedLevel; private PacketToken? _currentToken; + private bool _readToLast; /// /// Initializes a new instance of the struct. @@ -35,6 +36,7 @@ public struct PacketStringEnumerator _numberOfSeparators.Add(separator, 1); _currentToken = null; _preparedLevel = null; + _readToLast = false; } /// @@ -52,6 +54,7 @@ public struct PacketStringEnumerator _numberOfSeparators = new Dictionary(numberOfSeparators); _currentToken = null; _preparedLevel = null; + _readToLast = false; } /// @@ -67,6 +70,14 @@ public struct PacketStringEnumerator _currentLevel.SeparatorOnce = separator; } + /// + /// Sets that the next token should be read to the last entry in the level. + /// + public void SetReadToLast() + { + _readToLast = true; + } + /// /// Prepare the given level that can be set when needed. /// @@ -220,7 +231,8 @@ public struct PacketStringEnumerator bool? isLast, encounteredUpperLevel; // If the current character is a separator, stop, else, add it to the builder. - while (!IsSeparator(currentCharacter, out isLast, out encounteredUpperLevel)) + // If should read to last, then read until isLast is null or true. + while (!IsSeparator(currentCharacter, out isLast, out encounteredUpperLevel) || (_readToLast && !(isLast ?? true))) { tokenString.Append(currentCharacter); currentIndex++; @@ -235,6 +247,7 @@ public struct PacketStringEnumerator currentCharacter = _data.Data[currentIndex]; } + _readToLast = false; currentIndex++; var token = new PacketToken(tokenString.ToString(), isLast, encounteredUpperLevel, _data.ReachedEnd); -- 2.49.0