// // 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 Microsoft.CodeAnalysis.CSharp.Syntax; using NosSmooth.PacketSerializersGenerator.AttributeGenerators; using NosSmooth.PacketSerializersGenerator.Data; using NosSmooth.PacketSerializersGenerator.Errors; using NosSmooth.PacketSerializersGenerator.Extensions; namespace NosSmooth.PacketSerializersGenerator; /// /// Code generator of a packet converter. /// public class PacketConverterGenerator { private readonly PacketInfo _packetInfo; private readonly IReadOnlyList _parameterGenerators; /// /// Initializes a new instance of the class. /// /// The packet type information. /// The converter parameter generators. public PacketConverterGenerator(PacketInfo packetInfo, IReadOnlyList parameterGenerators) { _packetInfo = packetInfo; _parameterGenerators = parameterGenerators; } /// /// Generate the converter class. /// /// The text writer to write the class to. /// An error, if any. public IError? Generate(IndentedTextWriter textWriter) { var usings = _packetInfo.PacketRecord.SyntaxTree.GetRoot() .DescendantNodes() .OfType() .Select(x => x.ToString()) .ToList(); usings.Add($"using {_packetInfo.Namespace};"); usings.Add("using NosSmooth.PacketSerializer.Abstractions.Errors;"); usings.Add("using NosSmooth.PacketSerializer.Abstractions;"); usings.Add("using Remora.Results;"); var usingsString = string.Join("\n", usings.Distinct()); textWriter.WriteLine ( @$"// #nullable enable #pragma warning disable 1591 {usingsString} namespace {_packetInfo.Namespace}.Generated; public class {_packetInfo.Name}Converter : BaseStringConverter<{_packetInfo.Name}> {{" ); textWriter.Indent++; textWriter.WriteLine ( $@" private readonly IStringSerializer _stringSerializer; public {_packetInfo.Name}Converter(IStringSerializer stringSerializer) {{ _stringSerializer = stringSerializer; }} /// public override Result Serialize({_packetInfo.Name}? obj, ref 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 ( $@" }} /// public override Result<{_packetInfo.Name}?> Deserialize(ref PacketStringEnumerator stringEnumerator, DeserializeOptions options) {{ var typeConverter = this; " ); textWriter.Indent++; var deserializerError = GenerateDeserializer(textWriter); if (deserializerError is not null) { return deserializerError; } textWriter.Indent--; textWriter.WriteLine ( $@" }} }}" ); 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 checkError = generator.CheckParameter(_packetInfo, parameter); if (checkError is not null) { return checkError; } var serializerError = generator.GenerateSerializerPart(textWriter, _packetInfo); if (serializerError is not null) { return serializerError; } 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) { if (_packetInfo.Parameters.List.Count == 0) { textWriter.WriteLine ($"return new {_packetInfo.Name}();"); return null; } _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 skipResult;"); textWriter.WriteLine("IResultError? skipError;"); skipped = true; } textWriter.WriteLine($@"skipResult = stringEnumerator.GetNextToken(out _);"); 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 ( "SGInd", "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 ( new[] { parameter.PacketIndex.ToString(), parameter.Name, _packetInfo.Name } ) ); } bool handled = false; foreach (var generator in _parameterGenerators) { if (generator.ShouldHandle(parameter)) { var checkError = generator.CheckParameter(_packetInfo, parameter); if (checkError is not null) { return checkError; } 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; } }