~ruther/NosSmooth

f6b03188c07779c88ec7587117b26c130243b5e9 — František Boháček 3 years ago 082912a
feat: add inline packet converters generation support
18 files changed, 531 insertions(+), 69 deletions(-)

M Core/NosSmooth.PacketSerializersGenerator/AttributeGenerators/PacketConditionalIndexAttributeGenerator.cs
M Core/NosSmooth.PacketSerializersGenerator/AttributeGenerators/PacketContextListAttributeGenerator.cs
M Core/NosSmooth.PacketSerializersGenerator/AttributeGenerators/PacketGreedyIndexAttributeGenerator.cs
M Core/NosSmooth.PacketSerializersGenerator/AttributeGenerators/PacketIndexAttributeGenerator.cs
M Core/NosSmooth.PacketSerializersGenerator/AttributeGenerators/PacketListIndexAttributeGenerator.cs
M Core/NosSmooth.PacketSerializersGenerator/ConverterDeserializationGenerator.cs
M Core/NosSmooth.PacketSerializersGenerator/ConverterSerializationGenerator.cs
M Core/NosSmooth.PacketSerializersGenerator/Data/PacketInfo.cs
M Core/NosSmooth.PacketSerializersGenerator/Extensions/ParameterInfoExtensions.cs
A Core/NosSmooth.PacketSerializersGenerator/InlineTypeConverterGenerator.cs
M Core/NosSmooth.PacketSerializersGenerator/SourceGenerator.cs
A Core/NosSmooth.PacketSerializersGenerator/TypeGenerators/BasicTypeGenerator.cs
A Core/NosSmooth.PacketSerializersGenerator/TypeGenerators/BoolTypeGenerator.cs
A Core/NosSmooth.PacketSerializersGenerator/TypeGenerators/EnumTypeGenerator.cs
A Core/NosSmooth.PacketSerializersGenerator/TypeGenerators/FallbackInlineConverterGenerator.cs
A Core/NosSmooth.PacketSerializersGenerator/TypeGenerators/IInlineConverterGenerator.cs
A Core/NosSmooth.PacketSerializersGenerator/TypeGenerators/StringInlineConverterGenerator.cs
M Core/NosSmooth.Packets/Attributes/GenerateSerializerAttribute.cs
M Core/NosSmooth.PacketSerializersGenerator/AttributeGenerators/PacketConditionalIndexAttributeGenerator.cs => Core/NosSmooth.PacketSerializersGenerator/AttributeGenerators/PacketConditionalIndexAttributeGenerator.cs +14 -4
@@ 16,6 16,17 @@ namespace NosSmooth.PacketSerializersGenerator.AttributeGenerators;
/// <inheritdoc />
public class PacketConditionalIndexAttributeGenerator : IParameterGenerator
{
    private readonly InlineTypeConverterGenerator _inlineTypeConverterGenerators;

    /// <summary>
    /// Initializes a new instance of the <see cref="PacketConditionalIndexAttributeGenerator"/> class.
    /// </summary>
    /// <param name="inlineTypeConverterGenerators">The generator for types.</param>
    public PacketConditionalIndexAttributeGenerator(InlineTypeConverterGenerator inlineTypeConverterGenerators)
    {
        _inlineTypeConverterGenerators = inlineTypeConverterGenerators;
    }

    /// <summary>
    /// Gets the full name of the packet index attribute.
    /// </summary>


@@ 207,7 218,7 @@ public class PacketConditionalIndexAttributeGenerator : IParameterGenerator
        }

        // serialize, check the error.
        generator.SerializeAndCheck(parameter);
        _inlineTypeConverterGenerators.SerializeAndCheck(textWriter, packetInfo);

        // pop inner separator level
        if (pushedLevel)


@@ 263,12 274,11 @@ public class PacketConditionalIndexAttributeGenerator : IParameterGenerator
            pushedLevel = true;
        }

        generator.DeserializeAndCheck
            ($"{packetInfo.Namespace}.{packetInfo.Name}", parameter, packetInfo.Parameters.IsLast);
        _inlineTypeConverterGenerators.DeserializeAndCheck(textWriter, packetInfo);

        if (!parameter.Nullable)
        {
            generator.CheckNullError(parameter.GetResultVariableName(), parameter.Name);
            generator.CheckNullError(parameter.GetNullableVariableName(), parameter.GetResultVariableName(), parameter.Name);
        }

        generator.AssignLocalVariable(parameter, false);

M Core/NosSmooth.PacketSerializersGenerator/AttributeGenerators/PacketContextListAttributeGenerator.cs => Core/NosSmooth.PacketSerializersGenerator/AttributeGenerators/PacketContextListAttributeGenerator.cs +14 -4
@@ 17,6 17,17 @@ namespace NosSmooth.PacketSerializersGenerator.AttributeGenerators;
/// <inheritdoc />
public class PacketContextListAttributeGenerator : IParameterGenerator
{
    private readonly InlineTypeConverterGenerator _inlineTypeConverterGenerators;

    /// <summary>
    /// Initializes a new instance of the <see cref="PacketContextListAttributeGenerator"/> class.
    /// </summary>
    /// <param name="inlineTypeConverterGenerators">The generator for types.</param>
    public PacketContextListAttributeGenerator(InlineTypeConverterGenerator inlineTypeConverterGenerators)
    {
        _inlineTypeConverterGenerators = inlineTypeConverterGenerators;
    }

    /// <summary>
    /// Gets the full name of the packet index attribute.
    /// </summary>


@@ 64,7 75,7 @@ public class PacketContextListAttributeGenerator : IParameterGenerator
        var innerSeparator = attribute.GetNamedValue<char>("InnerSeparator", '.');
        generator.PrepareLevel(innerSeparator);

        generator.SerializeAndCheck(parameter);
        _inlineTypeConverterGenerators.SerializeAndCheck(textWriter, packetInfo);
        generator.RemovePreparedLevel();
        generator.PopLevel();



@@ 104,13 115,12 @@ public class PacketContextListAttributeGenerator : IParameterGenerator
        var innerSeparator = attribute.GetNamedValue<char>("InnerSeparator", '.');
        generator.PrepareLevel(innerSeparator);

        generator.DeserializeAndCheck
            ($"{packetInfo.Namespace}.{packetInfo.Name}", parameter, packetInfo.Parameters.IsLast);
        _inlineTypeConverterGenerators.DeserializeAndCheck(textWriter, packetInfo);
        generator.RemovePreparedLevel();
        generator.PopLevel();
        if (!parameter.Nullable)
        {
            generator.CheckNullError(parameter.GetResultVariableName(), parameter.Name);
            generator.CheckNullError(parameter.GetNullableVariableName(), parameter.GetResultVariableName(), parameter.Name);
        }

        generator.AssignLocalVariable(parameter);

M Core/NosSmooth.PacketSerializersGenerator/AttributeGenerators/PacketGreedyIndexAttributeGenerator.cs => Core/NosSmooth.PacketSerializersGenerator/AttributeGenerators/PacketGreedyIndexAttributeGenerator.cs +8 -6
@@ 14,14 14,17 @@ namespace NosSmooth.PacketSerializersGenerator.AttributeGenerators;
/// <inheritdoc/>
public class PacketGreedyIndexAttributeGenerator : IParameterGenerator
{
    private PacketIndexAttributeGenerator _basicAttributeGenerator;
    private readonly PacketIndexAttributeGenerator _basicAttributeGenerator;
    private readonly InlineTypeConverterGenerator _inlineTypeConverterGenerators;

    /// <summary>
    /// Initializes a new instance of the <see cref="PacketGreedyIndexAttributeGenerator"/> class.
    /// </summary>
    public PacketGreedyIndexAttributeGenerator()
    /// <param name="inlineTypeConverterGenerators">The generator for types.</param>
    public PacketGreedyIndexAttributeGenerator(InlineTypeConverterGenerator inlineTypeConverterGenerators)
    {
        _basicAttributeGenerator = new PacketIndexAttributeGenerator();
        _basicAttributeGenerator = new PacketIndexAttributeGenerator(inlineTypeConverterGenerators);
        _inlineTypeConverterGenerators = inlineTypeConverterGenerators;
    }

    /// <summary>


@@ 79,12 82,11 @@ public class PacketGreedyIndexAttributeGenerator : IParameterGenerator
        }

        generator.SetReadToLast(); // Greedy
        generator.DeserializeAndCheck
            ($"{packetInfo.Namespace}.{packetInfo.Name}", parameter, packetInfo.Parameters.IsLast);
        _inlineTypeConverterGenerators.DeserializeAndCheck(textWriter, packetInfo);

        if (!parameter.Nullable)
        {
            generator.CheckNullError(parameter.GetResultVariableName(), parameter.Name);
            generator.CheckNullError(parameter.GetNullableVariableName(), parameter.GetResultVariableName(), parameter.Name);
        }

        generator.AssignLocalVariable(parameter, false);

M Core/NosSmooth.PacketSerializersGenerator/AttributeGenerators/PacketIndexAttributeGenerator.cs => Core/NosSmooth.PacketSerializersGenerator/AttributeGenerators/PacketIndexAttributeGenerator.cs +16 -5
@@ 8,12 8,24 @@ using System.CodeDom.Compiler;
using NosSmooth.PacketSerializersGenerator.Data;
using NosSmooth.PacketSerializersGenerator.Errors;
using NosSmooth.PacketSerializersGenerator.Extensions;
using NosSmooth.PacketSerializersGenerator.TypeGenerators;

namespace NosSmooth.PacketSerializersGenerator.AttributeGenerators;

/// <inheritdoc />
public class PacketIndexAttributeGenerator : IParameterGenerator
{
    private readonly InlineTypeConverterGenerator _inlineTypeConverterGenerators;

    /// <summary>
    /// Initializes a new instance of the <see cref="PacketIndexAttributeGenerator"/> class.
    /// </summary>
    /// <param name="inlineTypeConverterGenerators">The generator for types.</param>
    public PacketIndexAttributeGenerator(InlineTypeConverterGenerator inlineTypeConverterGenerators)
    {
        _inlineTypeConverterGenerators = inlineTypeConverterGenerators;
    }

    /// <summary>
    /// Gets the full name of the packet index attribute.
    /// </summary>


@@ 41,7 53,7 @@ public class PacketIndexAttributeGenerator : IParameterGenerator
        bool pushedLevel = false;
        var generator = new ConverterSerializationGenerator(textWriter);
        var parameter = packetInfo.Parameters.Current;
        var attribute = parameter.Attributes.First(x => x.FullName == PacketIndexAttributeFullName);
        var attribute = parameter.Attributes.First();

        if (parameter.IsOptional())
        {


@@ 66,7 78,7 @@ public class PacketIndexAttributeGenerator : IParameterGenerator
        }

        // serialize, check the error.
        generator.SerializeAndCheck(parameter);
        _inlineTypeConverterGenerators.SerializeAndCheck(textWriter, packetInfo);

        // pop inner separator level
        if (pushedLevel)


@@ 113,12 125,11 @@ public class PacketIndexAttributeGenerator : IParameterGenerator
            pushedLevel = true;
        }

        generator.DeserializeAndCheck
            ($"{packetInfo.Namespace}.{packetInfo.Name}", parameter, packetInfo.Parameters.IsLast);
        _inlineTypeConverterGenerators.DeserializeAndCheck(textWriter, packetInfo);

        if (!parameter.Nullable)
        {
            generator.CheckNullError(parameter.GetResultVariableName(), parameter.Name);
            generator.CheckNullError(parameter.GetNullableVariableName(), parameter.GetResultVariableName(), parameter.Name);
        }

        generator.AssignLocalVariable(parameter, false);

M Core/NosSmooth.PacketSerializersGenerator/AttributeGenerators/PacketListIndexAttributeGenerator.cs => Core/NosSmooth.PacketSerializersGenerator/AttributeGenerators/PacketListIndexAttributeGenerator.cs +14 -3
@@ 19,6 19,17 @@ namespace NosSmooth.PacketSerializersGenerator.AttributeGenerators;
/// <inheritdoc />
public class PacketListIndexAttributeGenerator : IParameterGenerator
{
    private readonly InlineTypeConverterGenerator _inlineTypeConverterGenerators;

    /// <summary>
    /// Initializes a new instance of the <see cref="PacketListIndexAttributeGenerator"/> class.
    /// </summary>
    /// <param name="inlineTypeConverterGenerators">The generator for types.</param>
    public PacketListIndexAttributeGenerator(InlineTypeConverterGenerator inlineTypeConverterGenerators)
    {
        _inlineTypeConverterGenerators = inlineTypeConverterGenerators;
    }

    /// <summary>
    /// Gets the full name of the packet index attribute.
    /// </summary>


@@ 66,7 77,7 @@ public class PacketListIndexAttributeGenerator : IParameterGenerator
        var innerSeparator = attribute.GetNamedValue<char>("InnerSeparator", '.');
        generator.PrepareLevel(innerSeparator);

        generator.SerializeAndCheck(parameter);
        _inlineTypeConverterGenerators.SerializeAndCheck(textWriter, packetInfo);
        generator.RemovePreparedLevel();
        generator.PopLevel();



@@ 108,12 119,12 @@ public class PacketListIndexAttributeGenerator : IParameterGenerator
        var innerSeparator = attribute.GetNamedValue<char>("InnerSeparator", '.');
        generator.PrepareLevel(innerSeparator);

        generator.DeserializeAndCheck($"{packetInfo.Namespace}.{packetInfo.Name}", parameter, packetInfo.Parameters.IsLast);
        _inlineTypeConverterGenerators.DeserializeAndCheck(textWriter, packetInfo);
        generator.RemovePreparedLevel();
        generator.PopLevel();
        if (!parameter.Nullable)
        {
            generator.CheckNullError(parameter.GetResultVariableName(), parameter.Name);
            generator.CheckNullError(parameter.GetNullableVariableName(), parameter.GetResultVariableName(), parameter.Name);
        }

        generator.AssignLocalVariable(parameter, false);

M Core/NosSmooth.PacketSerializersGenerator/ConverterDeserializationGenerator.cs => Core/NosSmooth.PacketSerializersGenerator/ConverterDeserializationGenerator.cs +5 -23
@@ 100,34 100,16 @@ public class ConverterDeserializationGenerator
    }

    /// <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="nullableVariableName">The variable to check for null.</param>
    /// <param name="resultVariableName">The result variable to use for the error.</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.")
    public void CheckNullError(string nullableVariableName, string resultVariableName, string parameterName, string reason = "The converter has returned null even though it was not expected.")
    {
        _textWriter.WriteMultiline($@"
if ({resultVariableName}.Entity is null) {{
if ({nullableVariableName} is null) {{
    return new PacketParameterSerializerError(this, ""{parameterName}"", {resultVariableName}, ""{reason}"");
}}
");


@@ 149,7 131,7 @@ if ({resultVariableName}.Entity is null) {{
    /// <param name="declare">Whether to also declare the local variable.</param>
    public void AssignLocalVariable(ParameterInfo parameter, bool declare = true)
    {
        _textWriter.WriteLine($"{(declare ? "var " : string.Empty)}{parameter.Name} = ({parameter.GetActualType()}){parameter.GetResultVariableName()}.Entity;");
        _textWriter.WriteLine($"{(declare ? "var " : string.Empty)}{parameter.Name} = ({parameter.GetActualType()}){parameter.GetNullableVariableName()};");
    }

    /// <summary>

M Core/NosSmooth.PacketSerializersGenerator/ConverterSerializationGenerator.cs => Core/NosSmooth.PacketSerializersGenerator/ConverterSerializationGenerator.cs +0 -18
@@ 71,22 71,4 @@ public class ConverterSerializationGenerator
    {
        _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

M Core/NosSmooth.PacketSerializersGenerator/Data/PacketInfo.cs => Core/NosSmooth.PacketSerializersGenerator/Data/PacketInfo.cs +1 -0
@@ 21,6 21,7 @@ namespace NosSmooth.PacketSerializersGenerator.Data;
public record PacketInfo
(
    Compilation Compilation,
    AttributeInfo GenerateAttribute,
    RecordDeclarationSyntax PacketRecord,
    SemanticModel SemanticModel,
    Parameters Parameters,

M Core/NosSmooth.PacketSerializersGenerator/Extensions/ParameterInfoExtensions.cs => Core/NosSmooth.PacketSerializersGenerator/Extensions/ParameterInfoExtensions.cs +10 -0
@@ 48,6 48,16 @@ public static class ParameterInfoExtensions
    }

    /// <summary>
    /// Gets the name of the nullable variable.
    /// </summary>
    /// <param name="parameterInfo">The parameter.</param>
    /// <returns>The name of the nullable variable.</returns>
    public static string GetNullableVariableName(this ParameterInfo parameterInfo)
    {
        return $"{parameterInfo.Name}Nullable";
    }

    /// <summary>
    /// Gets the type of the parameter as nullable.
    /// </summary>
    /// <param name="parameterInfo">The parameter.</param>

A Core/NosSmooth.PacketSerializersGenerator/InlineTypeConverterGenerator.cs => Core/NosSmooth.PacketSerializersGenerator/InlineTypeConverterGenerator.cs +79 -0
@@ 0,0 1,79 @@
//
//  InlineTypeConverterGenerator.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.Errors;
using NosSmooth.PacketSerializersGenerator.Extensions;
using NosSmooth.PacketSerializersGenerator.TypeGenerators;

namespace NosSmooth.PacketSerializersGenerator;

/// <summary>
/// Generates serializer or deserializer for a parameter of specified type.
/// </summary>
public class InlineTypeConverterGenerator
{
    private readonly IReadOnlyList<IInlineConverterGenerator> _typeGenerators;
    private readonly FallbackInlineConverterGenerator _fallbackInlineConverterGenerator;

    /// <summary>
    /// Initializes a new instance of the <see cref="InlineTypeConverterGenerator"/> class.
    /// </summary>
    /// <param name="typeGenerators">The type generators.</param>
    public InlineTypeConverterGenerator(IReadOnlyList<IInlineConverterGenerator> typeGenerators)
    {
        _typeGenerators = typeGenerators;
        _fallbackInlineConverterGenerator = new FallbackInlineConverterGenerator();

    }

    /// <summary>
    /// Generates deserialize and check code.
    /// </summary>
    /// <param name="textWriter">The text writer.</param>
    /// <param name="packet">The packet.</param>
    /// <returns>An error, if any.</returns>
    public IError? DeserializeAndCheck(IndentedTextWriter textWriter, PacketInfo packet)
    {
        var shouldGenerateInline = packet.GenerateAttribute.GetIndexedValue<bool>(0);
        if (shouldGenerateInline)
        {
            foreach (var generator in _typeGenerators)
            {
                if (generator.ShouldHandle(packet.Parameters.Current))
                {
                    return generator.GenerateDeserializerPart(textWriter, packet);
                }
            }
        }

        return _fallbackInlineConverterGenerator.GenerateDeserializerPart(textWriter, packet);
    }

    /// <summary>
    /// Generates serialize and check code.
    /// </summary>
    /// <param name="textWriter">The text writer.</param>
    /// <param name="packet">The packet.</param>
    /// <returns>An error, if any.</returns>
    public IError? SerializeAndCheck(IndentedTextWriter textWriter, PacketInfo packet)
    {
        var shouldGenerateInline = packet.GenerateAttribute.GetIndexedValue<bool>(0);
        if (shouldGenerateInline)
        {
            foreach (var generator in _typeGenerators)
            {
                if (generator.ShouldHandle(packet.Parameters.Current))
                {
                    return generator.GenerateSerializerPart(textWriter, packet);
                }
            }
        }

        return _fallbackInlineConverterGenerator.GenerateSerializerPart(textWriter, packet);
    }
}
\ No newline at end of file

M Core/NosSmooth.PacketSerializersGenerator/SourceGenerator.cs => Core/NosSmooth.PacketSerializersGenerator/SourceGenerator.cs +36 -6
@@ 14,6 14,7 @@ using NosSmooth.PacketSerializersGenerator.AttributeGenerators;
using NosSmooth.PacketSerializersGenerator.Data;
using NosSmooth.PacketSerializersGenerator.Errors;
using NosSmooth.PacketSerializersGenerator.Extensions;
using NosSmooth.PacketSerializersGenerator.TypeGenerators;

namespace NosSmooth.PacketSerializersGenerator;



@@ 31,15 32,28 @@ public class SourceGenerator : ISourceGenerator
    /// </summary>
    public SourceGenerator()
    {
        var typeGenerators = new List<IInlineConverterGenerator>
        (
            new IInlineConverterGenerator[]
            {
                new StringInlineConverterGenerator(),
                new BasicTypeGenerator(),
                new EnumTypeGenerator(),
                new BoolTypeGenerator(),
            }
        );

        var inlineTypeConverter = new InlineTypeConverterGenerator(typeGenerators);

        _generators = new List<IParameterGenerator>
        (
            new IParameterGenerator[]
            {
                new PacketIndexAttributeGenerator(),
                new PacketGreedyIndexAttributeGenerator(),
                new PacketListIndexAttributeGenerator(),
                new PacketContextListAttributeGenerator(),
                new PacketConditionalIndexAttributeGenerator(),
                new PacketIndexAttributeGenerator(inlineTypeConverter),
                new PacketGreedyIndexAttributeGenerator(inlineTypeConverter),
                new PacketListIndexAttributeGenerator(inlineTypeConverter),
                new PacketContextListAttributeGenerator(inlineTypeConverter),
                new PacketConditionalIndexAttributeGenerator(inlineTypeConverter),
            }
        );
    }


@@ 115,6 129,11 @@ public class SourceGenerator : ISourceGenerator
                    $"{packetRecord.Identifier.NormalizeWhitespace().ToFullString()}Converter.g.cs",
                    stringWriter.GetStringBuilder().ToString()
                );
                File.WriteAllText
                (
                    $"/tmp/{packetRecord.Identifier.NormalizeWhitespace().ToFullString()}Converter.g.cs",
                    stringWriter.GetStringBuilder().ToString()
                );
            }
        }
    }


@@ 177,9 196,19 @@ public class SourceGenerator : ISourceGenerator
        }

        orderedParameters = orderedParameters.OrderBy(x => x.PacketIndex).ToList();
        var generatorAttribute = packetClass.AttributeLists.Where
                (x => x.ContainsAttribute(semanticModel, Constants.GenerateSourceAttributeClass))
            .Select
            (
                x => x.Attributes.First
                    (x => semanticModel.GetTypeInfo(x).Type!.ToString() == Constants.GenerateSourceAttributeClass)
            )
            .First();

        var packetInfo = new PacketInfo
        (
            compilation,
            CreateAttributeInfo(generatorAttribute, semanticModel),
            packetClass,
            semanticModel,
            new Parameters(orderedParameters),


@@ 271,7 300,8 @@ public class SourceGenerator : ISourceGenerator

            if (argumentName is not null)
            {
                namedArguments.Add(argumentName, new AttributeArgumentInfo(argument, isArray, value, argument.ToString()));
                namedArguments.Add
                    (argumentName, new AttributeArgumentInfo(argument, isArray, value, argument.ToString()));
            }
            else
            {

A Core/NosSmooth.PacketSerializersGenerator/TypeGenerators/BasicTypeGenerator.cs => Core/NosSmooth.PacketSerializersGenerator/TypeGenerators/BasicTypeGenerator.cs +66 -0
@@ 0,0 1,66 @@
//
//  BasicTypeGenerator.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.Errors;
using NosSmooth.PacketSerializersGenerator.Extensions;

namespace NosSmooth.PacketSerializersGenerator.TypeGenerators;

/// <summary>
/// Serializes and deserializes
/// long, ulong, int, uint, short, ushort, byte, sbyte.
/// </summary>
public class BasicTypeGenerator : IInlineConverterGenerator
{
    /// <summary>
    /// The list of the types to handle.
    /// </summary>
    public static IReadOnlyList<string> HandleTypes => new[] { "long", "ulong", "int", "uint", "short", "ushort", "byte", "sbyte" };

    /// <inheritdoc />
    public bool ShouldHandle(ParameterInfo parameter)
        => HandleTypes.Contains(parameter.Parameter.Type!.ToString());

    /// <inheritdoc />
    public IError? GenerateSerializerPart(IndentedTextWriter textWriter, PacketInfo packet)
    {
        var parameter = packet.Parameters.Current;
        textWriter.WriteLine
            ($"builder.Append(obj.{parameter.Name}{(parameter.Nullable ? "?" : string.Empty)}.ToString() ?? \"-\");");
        return null;
    }

    /// <inheritdoc />
    public IError? GenerateDeserializerPart(IndentedTextWriter textWriter, PacketInfo packet)
    {
        var parameter = packet.Parameters.Current;
        var type = parameter.Parameter.Type!.ToString();
        string isLastString = packet.Parameters.IsLast ? "true" : "false";
        textWriter.WriteMultiline
        (
            $@"
var {parameter.GetResultVariableName()} = stringEnumerator.GetNextToken();
var {parameter.GetErrorVariableName()} = CheckDeserializationResult({parameter.GetResultVariableName()}, ""{parameter.Name}"", stringEnumerator, {isLastString});
if ({parameter.GetErrorVariableName()} is not null)
{{
    return Result<{packet.Name}?>.FromError({parameter.GetErrorVariableName()}, {parameter.GetResultVariableName()});
}}
{parameter.GetVariableName()} = default;
{parameter.GetNullableType()} {parameter.GetNullableVariableName()};
if ({parameter.GetResultVariableName()}.Entity.Token == ""-"") {{
    {parameter.GetNullableVariableName()} = null;
}}
else if (!{type}.TryParse({parameter.GetResultVariableName()}.Entity.Token, out {parameter.GetVariableName()})) {{
    return new PacketParameterSerializerError(this, ""{parameter.Name}"", {parameter.GetResultVariableName()}, $""Could not convert {{{parameter.GetResultVariableName()}.Entity.Token}} as {type} in inline converter"");
}}
{parameter.GetNullableVariableName()} = {parameter.GetVariableName()};
"
        );
        return null;
    }
}
\ No newline at end of file

A Core/NosSmooth.PacketSerializersGenerator/TypeGenerators/BoolTypeGenerator.cs => Core/NosSmooth.PacketSerializersGenerator/TypeGenerators/BoolTypeGenerator.cs +59 -0
@@ 0,0 1,59 @@
//
//  BoolTypeGenerator.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.Errors;
using NosSmooth.PacketSerializersGenerator.Extensions;

namespace NosSmooth.PacketSerializersGenerator.TypeGenerators;

/// <inheritdoc />
public class BoolTypeGenerator : IInlineConverterGenerator
{
    /// <inheritdoc />
    public bool ShouldHandle(ParameterInfo parameter)
        => parameter.Parameter.Type!.ToString() == "bool";

    /// <inheritdoc />
    public IError? GenerateSerializerPart(IndentedTextWriter textWriter, PacketInfo packet)
    {
        var parameter = packet.Parameters.Current;
        if (parameter.Nullable)
        {
            textWriter.WriteLine($"if (obj.{parameter.Name} is null)");
            textWriter.WriteLine("{");
            textWriter.Indent++;
            textWriter.WriteLine("builder.Append(\"-\");");
            textWriter.Indent--;
            textWriter.WriteLine("}");
            textWriter.WriteLine("else");
        }
        textWriter.WriteLine("{");
        textWriter.Indent++;
        textWriter.WriteLine($"builder.Append(obj.{parameter.Name} ? \"1\" : \"0\");");
        textWriter.Indent--;
        textWriter.WriteLine("}");
        return null;
    }

    /// <inheritdoc />
    public IError? GenerateDeserializerPart(IndentedTextWriter textWriter, PacketInfo packet)
    {
        var parameter = packet.Parameters.Current;
        string isLastString = packet.Parameters.IsLast ? "true" : "false";
        textWriter.WriteMultiline($@"
var {parameter.GetResultVariableName()} = stringEnumerator.GetNextToken();
var {parameter.GetErrorVariableName()} = CheckDeserializationResult({parameter.GetResultVariableName()}, ""{parameter.Name}"", stringEnumerator, {isLastString});
if ({parameter.GetErrorVariableName()} is not null)
{{
    return Result<{packet.Name}?>.FromError({parameter.GetErrorVariableName()}, {parameter.GetResultVariableName()});
}}
{parameter.GetNullableType()} {parameter.GetNullableVariableName()} = {parameter.GetResultVariableName()}.Entity.Token == ""1"" ? true : ({parameter.GetResultVariableName()}.Entity.Token == ""-"" ? null : false);
");
        return null;
    }
}
\ No newline at end of file

A Core/NosSmooth.PacketSerializersGenerator/TypeGenerators/EnumTypeGenerator.cs => Core/NosSmooth.PacketSerializersGenerator/TypeGenerators/EnumTypeGenerator.cs +61 -0
@@ 0,0 1,61 @@
//
//  EnumTypeGenerator.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;
using NosSmooth.PacketSerializersGenerator.Data;
using NosSmooth.PacketSerializersGenerator.Errors;
using NosSmooth.PacketSerializersGenerator.Extensions;

namespace NosSmooth.PacketSerializersGenerator.TypeGenerators;

/// <inheritdoc />
public class EnumTypeGenerator : IInlineConverterGenerator
{
    /// <inheritdoc />
    public bool ShouldHandle(ParameterInfo parameter)
        => parameter.Type.TypeKind == TypeKind.Enum;

    /// <inheritdoc />
    public IError? GenerateSerializerPart(IndentedTextWriter textWriter, PacketInfo packet)
    {
        var parameter = packet.Parameters.Current;
        var underlyingType = ((INamedTypeSymbol)parameter.Type).EnumUnderlyingType!.ToString();
        textWriter.WriteLine
            ($"builder.Append((({underlyingType}?)obj.{parameter.Name}{(parameter.Nullable ? "?" : string.Empty)}).ToString() ?? \"-\");");
        return null;
    }

    /// <inheritdoc />
    public IError? GenerateDeserializerPart(IndentedTextWriter textWriter, PacketInfo packet)
    {
        var parameter = packet.Parameters.Current;
        var underlyingType = ((INamedTypeSymbol)parameter.Type).EnumUnderlyingType!.ToString();
        string isLastString = packet.Parameters.IsLast ? "true" : "false";
        textWriter.WriteMultiline
        (
            $@"
var {parameter.GetResultVariableName()} = stringEnumerator.GetNextToken();
var {parameter.GetErrorVariableName()} = CheckDeserializationResult({parameter.GetResultVariableName()}, ""{parameter.Name}"", stringEnumerator, {isLastString});
if ({parameter.GetErrorVariableName()} is not null)
{{
    return Result<{packet.Name}?>.FromError({parameter.GetErrorVariableName()}, {parameter.GetResultVariableName()});
}}
{parameter.GetVariableName()} = default;
{underlyingType} {parameter.GetVariableName()}Underlying = default;
{parameter.GetNullableType()} {parameter.GetNullableVariableName()};
if ({parameter.GetResultVariableName()}.Entity.Token == ""-"") {{
    {parameter.GetNullableVariableName()} = null;
}}
else if (!{underlyingType}.TryParse({parameter.GetResultVariableName()}.Entity.Token, out {parameter.GetVariableName()}Underlying)) {{
    return new PacketParameterSerializerError(this, ""{parameter.Name}"", {parameter.GetResultVariableName()}, $""Could not convert {{{parameter.GetResultVariableName()}.Entity.Token}} as {underlyingType} in inline converter"");
}}
{parameter.GetNullableVariableName()} = ({parameter.GetNullableType()}){parameter.GetVariableName()}Underlying;
"
        );
        return null;
    }
}
\ No newline at end of file

A Core/NosSmooth.PacketSerializersGenerator/TypeGenerators/FallbackInlineConverterGenerator.cs => Core/NosSmooth.PacketSerializersGenerator/TypeGenerators/FallbackInlineConverterGenerator.cs +56 -0
@@ 0,0 1,56 @@
//
//  FallbackInlineConverterGenerator.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.Errors;
using NosSmooth.PacketSerializersGenerator.Extensions;

namespace NosSmooth.PacketSerializersGenerator.TypeGenerators;

/// <inheritdoc />
public class FallbackInlineConverterGenerator : IInlineConverterGenerator
{
    /// <inheritdoc />
    public bool ShouldHandle(ParameterInfo parameter)
    {
        return true;
    }

    /// <inheritdoc />
    public IError? GenerateSerializerPart(IndentedTextWriter textWriter, PacketInfo packet)
    {
        var parameter = packet.Parameters.Current;
        textWriter.WriteMultiline
        (
            $@"
var {parameter.GetResultVariableName()} = _typeConverterRepository.Serialize<{parameter.GetActualType()}>(obj.{parameter.Name}, builder);
if (!{parameter.GetResultVariableName()}.IsSuccess)
{{
    return Result.FromError(new PacketParameterSerializerError(this, ""{parameter.Name}"", {parameter.GetResultVariableName()}), {parameter.GetResultVariableName()});
}}
"
        );
        return null;
    }

    /// <inheritdoc />
    public IError? GenerateDeserializerPart(IndentedTextWriter textWriter, PacketInfo packet)
    {
        var parameter = packet.Parameters.Current;
        string isLastString = packet.Parameters.IsLast ? "true" : "false";
        textWriter.WriteMultiline($@"
var {parameter.GetResultVariableName()} = _typeConverterRepository.Deserialize<{parameter.GetNullableType()}>(stringEnumerator);
var {parameter.GetErrorVariableName()} = CheckDeserializationResult({parameter.GetResultVariableName()}, ""{parameter.Name}"", stringEnumerator, {isLastString});
if ({parameter.GetErrorVariableName()} is not null)
{{
    return Result<{packet.Name}?>.FromError({parameter.GetErrorVariableName()}, {parameter.GetResultVariableName()});
}}
var {parameter.GetNullableVariableName()} = {parameter.GetResultVariableName()}.Entity;
");
        return null;
    }
}
\ No newline at end of file

A Core/NosSmooth.PacketSerializersGenerator/TypeGenerators/IInlineConverterGenerator.cs => Core/NosSmooth.PacketSerializersGenerator/TypeGenerators/IInlineConverterGenerator.cs +40 -0
@@ 0,0 1,40 @@
//
//  IInlineConverterGenerator.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.Errors;

namespace NosSmooth.PacketSerializersGenerator.TypeGenerators;

/// <summary>
/// Generates inline type converters.
/// </summary>
public interface IInlineConverterGenerator
{
    /// <summary>
    /// Whether the given parameter should be handled by this.
    /// </summary>
    /// <param name="parameter">The parameter.</param>
    /// <returns>Whethet to handle.</returns>
    public bool ShouldHandle(ParameterInfo parameter);

    /// <summary>
    /// Generate the serializer part.
    /// </summary>
    /// <param name="textWriter">The text writer to write to.</param>
    /// <param name="packet">The packet.</param>
    /// <returns>An error, if any.</returns>
    public IError? GenerateSerializerPart(IndentedTextWriter textWriter, PacketInfo packet);

    /// <summary>
    /// Generate the deserializer part.
    /// </summary>
    /// <param name="textWriter">The text writer to write to.</param>
    /// <param name="packet">The packet.</param>
    /// <returns>An error, if any.</returns>
    public IError? GenerateDeserializerPart(IndentedTextWriter textWriter, PacketInfo packet);
}
\ No newline at end of file

A Core/NosSmooth.PacketSerializersGenerator/TypeGenerators/StringInlineConverterGenerator.cs => Core/NosSmooth.PacketSerializersGenerator/TypeGenerators/StringInlineConverterGenerator.cs +45 -0
@@ 0,0 1,45 @@
//
//  StringInlineConverterGenerator.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.Errors;
using NosSmooth.PacketSerializersGenerator.Extensions;

namespace NosSmooth.PacketSerializersGenerator.TypeGenerators;

/// <inheritdoc />
public class StringInlineConverterGenerator : IInlineConverterGenerator
{
    /// <inheritdoc />
    public bool ShouldHandle(ParameterInfo parameter)
        => parameter.Parameter.Type!.ToString() == "string";

    /// <inheritdoc />
    public IError? GenerateSerializerPart(IndentedTextWriter textWriter, PacketInfo packet)
    {
        var parameter = packet.Parameters.Current;
        textWriter.WriteLine($"builder.Append(obj.{parameter.Name} ?? \"-\");");
        return null;
    }

    /// <inheritdoc />
    public IError? GenerateDeserializerPart(IndentedTextWriter textWriter, PacketInfo packet)
    {
        var parameter = packet.Parameters.Current;
        string isLastString = packet.Parameters.IsLast ? "true" : "false";
        textWriter.WriteMultiline($@"
var {parameter.GetResultVariableName()} = stringEnumerator.GetNextToken();
var {parameter.GetErrorVariableName()} = CheckDeserializationResult({parameter.GetResultVariableName()}, ""{parameter.Name}"", stringEnumerator, {isLastString});
if ({parameter.GetErrorVariableName()} is not null)
{{
    return Result<{packet.Name}?>.FromError({parameter.GetErrorVariableName()}, {parameter.GetResultVariableName()});
}}
var {parameter.GetNullableVariableName()} = {parameter.GetResultVariableName()}.Entity.Token;
");
        return null;
    }
}
\ No newline at end of file

M Core/NosSmooth.Packets/Attributes/GenerateSerializerAttribute.cs => Core/NosSmooth.Packets/Attributes/GenerateSerializerAttribute.cs +7 -0
@@ 14,4 14,11 @@ namespace NosSmooth.Packets.Attributes;
[AttributeUsage(AttributeTargets.Class)]
public class GenerateSerializerAttribute : Attribute
{
    /// <summary>
    /// Initializes a new instance of the <see cref="GenerateSerializerAttribute"/> class.
    /// </summary>
    /// <param name="allowInlineConverters">If true, the generator will try to generate parameter serializers for types like string, int, long so the type converter does not have to be called. This will increase performance, but the user will lose the ability to make custom serialization of these fields. Fields that may not be handled will still call the TypeConverter.</param>
    public GenerateSerializerAttribute(bool allowInlineConverters)
    {
    }
}
\ No newline at end of file

Do not follow this link