//
// 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;
/// <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)
{
var usings = _packetInfo.PacketRecord.SyntaxTree.GetRoot()
.DescendantNodes()
.OfType<UsingDirectiveSyntax>()
.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
(
@$"// <auto-generated/>
#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;
}}
/// <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(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<PacketToken> 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<object?>
(
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;
}
}