A Core/NosSmooth.PacketSerializersGenerator/AttributeGenerators/IParameterGenerator.cs => Core/NosSmooth.PacketSerializersGenerator/AttributeGenerators/IParameterGenerator.cs +43 -0
@@ 0,0 1,43 @@
+//
+// IParameterGenerator.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.Errors;
+
+namespace NosSmooth.PacketSerializersGenerator.AttributeGenerators
+{
+ /// <summary>
+ /// Generate serializer and deserializer method parts for the given constructor parameter.
+ /// </summary>
+ public interface IParameterGenerator
+ {
+ /// <summary>
+ /// Check whether this generator should handle parameter with this attribute.
+ /// </summary>
+ /// <param name="attribute">The parameters attribute.</param>
+ /// <returns>Whether to handle this parameter.</returns>
+ public bool ShouldHandle(AttributeSyntax attribute);
+
+ /// <summary>
+ /// Generate part for the Serializer method to serialize the given parameter.
+ /// </summary>
+ /// <param name="textWriter">The text writer to write the code to.</param>
+ /// <param name="recordDeclarationSyntax">The packet record declaration syntax.</param>
+ /// <param name="parameterInfo">The parameter info to generate for.</param>
+ /// <returns>The generated source code.</returns>
+ public IError? GenerateSerializerPart(IndentedTextWriter textWriter, RecordDeclarationSyntax recordDeclarationSyntax, ParameterInfo parameterInfo);
+
+ /// <summary>
+ /// Generate part for the Deserializer method to deserialize the given parameter.
+ /// </summary>
+ /// <param name="textWriter">The text writer to write the code to.</param>
+ /// <param name="recordDeclarationSyntax">The packet record declaration syntax.</param>
+ /// <param name="parameterInfo">The parameter info to generate for.</param>
+ /// <returns>The generated source code.</returns>
+ public IError? GenerateDeserializerPart(IndentedTextWriter textWriter, RecordDeclarationSyntax recordDeclarationSyntax, ParameterInfo parameterInfo);
+ }
+}<
\ No newline at end of file
A Core/NosSmooth.PacketSerializersGenerator/AttributeGenerators/PacketIndexAttributeGenerator.cs => Core/NosSmooth.PacketSerializersGenerator/AttributeGenerators/PacketIndexAttributeGenerator.cs +73 -0
@@ 0,0 1,73 @@
+//
+// PacketIndexAttributeGenerator.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 Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using NosSmooth.PacketSerializersGenerator.Errors;
+
+namespace NosSmooth.PacketSerializersGenerator.AttributeGenerators
+{
+ /// <inheritdoc />
+ public class PacketIndexAttributeGenerator : IParameterGenerator
+ {
+ /// <inheritdoc />
+ public bool ShouldHandle(AttributeSyntax attribute)
+ => attribute.Name.NormalizeWhitespace().ToFullString() == "PacketIndex";
+
+ /// <inheritdoc />
+ public IError? GenerateSerializerPart(IndentedTextWriter textWriter, RecordDeclarationSyntax recordDeclarationSyntax, ParameterInfo parameterInfo)
+ {
+ textWriter.WriteLine($@"
+var {parameterInfo.Name}Result = _typeConverterRepository.Serialize(obj.{parameterInfo.Name}, builder);
+if (!{parameterInfo.Name}Result.IsSuccess)
+{{
+ return Result.FromError(new PacketParameterSerializerError(this, ""{parameterInfo.Name}"", {parameterInfo.Name}Result), {parameterInfo.Name}Result);
+}}
+");
+
+ return null;
+ }
+
+ /// <inheritdoc />
+ public IError? GenerateDeserializerPart(IndentedTextWriter textWriter, RecordDeclarationSyntax recordDeclarationSyntax, ParameterInfo parameterInfo)
+ {
+ bool pushedLevel = false;
+
+ if (parameterInfo.NamedAttributeArguments.ContainsKey("AfterSeparator") && parameterInfo.NamedAttributeArguments["AfterSeparator"] is not null)
+ {
+ textWriter.WriteLine($"stringEnumerator.SetAfterSeparatorOnce('{parameterInfo.NamedAttributeArguments["AfterSeparator"]}');");
+ }
+
+ if (parameterInfo.NamedAttributeArguments.ContainsKey("InnerSeparator") && parameterInfo.NamedAttributeArguments["InnerSeparator"] is not null)
+ {
+ pushedLevel = true;
+ textWriter.WriteLine($"stringEnumerator.PushLevel('{parameterInfo.NamedAttributeArguments["InnerSeparator"]}');");
+ }
+
+ var semanticModel = parameterInfo.Compilation.GetSemanticModel(recordDeclarationSyntax.SyntaxTree);
+ var type = semanticModel.GetTypeInfo(parameterInfo.Parameter.Type!).Type;
+ string last = parameterInfo.IsLast ? "true" : "false";
+ textWriter.WriteLine($@"
+var {parameterInfo.Name}Result = _typeConverterRepository.Deserialize<{type!.ToString()}>(stringEnumerator);
+var {parameterInfo.Name}Error = CheckDeserializationResult({parameterInfo.Name}Result, ""{parameterInfo.Name}"", stringEnumerator, {last});
+if ({parameterInfo.Name}Error is not null)
+{{
+ return Result<{recordDeclarationSyntax.Identifier.NormalizeWhitespace().ToFullString()}?>.FromError({parameterInfo.Name}Error, {parameterInfo.Name}Result);
+}}
+
+var {parameterInfo.Name} = {parameterInfo.Name}Result.Entity;
+");
+ if (pushedLevel)
+ {
+ textWriter.WriteLine("stringEnumerator.PopLevel();");
+ }
+
+ return null;
+ }
+ }
+}<
\ No newline at end of file
A Core/NosSmooth.PacketSerializersGenerator/Errors/DiagnosticError.cs => Core/NosSmooth.PacketSerializersGenerator/Errors/DiagnosticError.cs +25 -0
@@ 0,0 1,25 @@
+//
+// DiagnosticError.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 Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Text;
+
+namespace NosSmooth.PacketSerializersGenerator.Errors;
+
+/// <summary>
+/// The diagnostic error.
+/// </summary>
+/// <param name="Id"></param>
+/// <param name="Title"></param>
+/// <param name="MessageFormat"></param>
+/// <param name="Tree"></param>
+/// <param name="Span"></param>
+/// <param name="Parameters"></param>
+public record DiagnosticError(string Id, string Title, string MessageFormat, SyntaxTree Tree, TextSpan Span, List<object?> Parameters) : IError
+{
+ /// <inheritdoc />
+ public string Message => Title;
+}<
\ No newline at end of file
A Core/NosSmooth.PacketSerializersGenerator/Errors/IError.cs => Core/NosSmooth.PacketSerializersGenerator/Errors/IError.cs +18 -0
@@ 0,0 1,18 @@
+//
+// IError.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.PacketSerializersGenerator.Errors;
+
+/// <summary>
+/// Base error type.
+/// </summary>
+public interface IError
+{
+ /// <summary>
+ /// Gets the message.
+ /// </summary>
+ public string Message { get; }
+}<
\ No newline at end of file
A Core/NosSmooth.PacketSerializersGenerator/Extensions/SyntaxNodeExtensions.cs => Core/NosSmooth.PacketSerializersGenerator/Extensions/SyntaxNodeExtensions.cs +60 -0
@@ 0,0 1,60 @@
+//
+// SyntaxNodeExtensions.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.Text;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+namespace NosSmooth.PacketSerializersGenerator.Extensions;
+
+/// <summary>
+/// Extensions for <see cref="SyntaxNode"/>.
+/// </summary>
+public static class SyntaxNodeExtensions
+{
+ /// <summary>
+ /// Gets the prefix of the given member (class or namespace).
+ /// </summary>
+ /// <param name="member">The member to get prefix of.</param>
+ /// <returns>The full name.</returns>
+ public static string GetPrefix(this SyntaxNode? member)
+ {
+ if (member is null)
+ {
+ return string.Empty;
+ }
+
+ StringBuilder sb = new StringBuilder();
+ SyntaxNode node = member;
+
+ while (node.Parent != null)
+ {
+ node = node.Parent;
+
+ if (node is NamespaceDeclarationSyntax)
+ {
+ var namespaceDeclaration = (NamespaceDeclarationSyntax)node;
+
+ sb.Insert(0, ".");
+ sb.Insert(0, namespaceDeclaration.Name.ToString());
+ }
+ else if (node is FileScopedNamespaceDeclarationSyntax fileScopedNamespaceDeclarationSyntax)
+ {
+ sb.Insert(0, ".");
+ sb.Insert(0, fileScopedNamespaceDeclarationSyntax.Name.ToString());
+ }
+ else if (node is ClassDeclarationSyntax)
+ {
+ var classDeclaration = (ClassDeclarationSyntax)node;
+
+ sb.Insert(0, ".");
+ sb.Insert(0, classDeclaration.Identifier.ToString());
+ }
+ }
+
+ return sb.ToString().Trim('.');
+ }
+}<
\ No newline at end of file
A Core/NosSmooth.PacketSerializersGenerator/IsExternalInit.cs => Core/NosSmooth.PacketSerializersGenerator/IsExternalInit.cs +15 -0
@@ 0,0 1,15 @@
+//
+// IsExternalInit.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 System.Runtime.CompilerServices
+{
+ /// <summary>
+ /// Dummy.
+ /// </summary>
+ public class IsExternalInit
+ {
+ }
+}<
\ No newline at end of file
A Core/NosSmooth.PacketSerializersGenerator/NosSmooth.PacketSerializersGenerator.csproj => Core/NosSmooth.PacketSerializersGenerator/NosSmooth.PacketSerializersGenerator.csproj +22 -0
@@ 0,0 1,22 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <TargetFramework>netstandard2.0</TargetFramework>
+ <ImplicitUsings>enable</ImplicitUsings>
+ <Nullable>enable</Nullable>
+ <LangVersion>10</LangVersion>
+ <IsRoslynComponent>true</IsRoslynComponent>
+ <IncludeCopyLocalFilesOutputGroup>true</IncludeCopyLocalFilesOutputGroup>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.3">
+ <PrivateAssets>all</PrivateAssets>
+ <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
+ </PackageReference>
+ <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.0.1" />
+ <None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
+ <EmbeddedResource Include="$(PkgRemora_Results)\lib\netstandard2.0\*.dll" Visible="false" />
+ </ItemGroup>
+
+</Project>
A Core/NosSmooth.PacketSerializersGenerator/PacketClassReceiver.cs => Core/NosSmooth.PacketSerializersGenerator/PacketClassReceiver.cs +50 -0
@@ 0,0 1,50 @@
+//
+// PacketClassReceiver.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 Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+namespace NosSmooth.PacketSerializersGenerator
+{
+ /// <summary>
+ /// Syntax receiver of classes with generate attribute.
+ /// </summary>
+ public class PacketClassReceiver : ISyntaxReceiver
+ {
+ private readonly List<RecordDeclarationSyntax> _packetClasses;
+
+ /// <summary>
+ /// Gets the name of the attribute that indicates the packet should have a serializer generated.
+ /// </summary>
+ public static string AttributeFullName => "GenerateSerializer";
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="PacketClassReceiver"/> class.
+ /// </summary>
+ public PacketClassReceiver()
+ {
+ _packetClasses = new List<RecordDeclarationSyntax>();
+ }
+
+ /// <summary>
+ /// Gets the classes that should have serializers generated.
+ /// </summary>
+ public IReadOnlyList<RecordDeclarationSyntax> PacketClasses => _packetClasses;
+
+ /// <inheritdoc />
+ public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
+ {
+ if (syntaxNode.IsKind(SyntaxKind.RecordDeclaration) && syntaxNode is RecordDeclarationSyntax classDeclarationSyntax)
+ {
+ if (classDeclarationSyntax.AttributeLists.Any(x => x.Attributes.Any(x => x.Name.NormalizeWhitespace().ToFullString() == AttributeFullName)))
+ {
+ _packetClasses.Add(classDeclarationSyntax);
+ }
+ }
+ }
+ }
+}<
\ No newline at end of file
A Core/NosSmooth.PacketSerializersGenerator/PacketSerializerGenerator.cs => Core/NosSmooth.PacketSerializersGenerator/PacketSerializerGenerator.cs +353 -0
@@ 0,0 1,353 @@
+//
+// PacketSerializerGenerator.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 System.Diagnostics;
+using System.Text;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using NosSmooth.PacketSerializersGenerator.AttributeGenerators;
+using NosSmooth.PacketSerializersGenerator.Errors;
+using NosSmooth.PacketSerializersGenerator.Extensions;
+
+namespace NosSmooth.PacketSerializersGenerator;
+
+/// <summary>
+/// Generates ITypeGenerator for packets that are marked with NosSmooth.Packets.Attributes.GenerateSerializerAttribute.
+/// </summary>
+/// <remarks>
+/// The packets to create serializer for have to be records that specify PacketIndices in the constructor.
+/// </remarks>
+[Generator]
+public class PacketSerializerGenerator : ISourceGenerator
+{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="PacketSerializerGenerator"/> class.
+ /// </summary>
+ public PacketSerializerGenerator()
+ {
+ _generators = new List<IParameterGenerator>(new[] { new PacketIndexAttributeGenerator() });
+ }
+
+ private readonly List<IParameterGenerator> _generators;
+
+ /// <inheritdoc />
+ public void Initialize(GeneratorInitializationContext context)
+ {
+ context.RegisterForSyntaxNotifications(() => new PacketClassReceiver());
+ }
+
+ /// <inheritdoc />
+ public void Execute(GeneratorExecutionContext context)
+ {
+ var syntaxReceiver = (PacketClassReceiver)context.SyntaxReceiver!;
+
+ foreach (var packetClass in syntaxReceiver.PacketClasses)
+ {
+ if (packetClass is not null)
+ {
+ using var stringWriter = new StringWriter();
+ var writer = new IndentedTextWriter(stringWriter, " ");
+ var generatedResult = GeneratePacketSerializer(writer, context.Compilation, packetClass);
+ if (generatedResult is not null)
+ {
+ if (generatedResult is DiagnosticError diagnosticError)
+ {
+ context.ReportDiagnostic(Diagnostic.Create
+ (
+ new DiagnosticDescriptor
+ (
+ diagnosticError.Id,
+ diagnosticError.Title,
+ diagnosticError.MessageFormat,
+ "Serialization",
+ DiagnosticSeverity.Error,
+ true
+ ),
+ Location.Create(diagnosticError.Tree, diagnosticError.Span),
+ diagnosticError.Parameters.ToArray()
+ )
+ );
+ }
+ else if (generatedResult is not null)
+ {
+ throw new Exception(generatedResult.Message);
+ }
+
+ continue;
+ }
+
+ context.AddSource($"{packetClass.Identifier.NormalizeWhitespace().ToFullString()}Converter.g.cs", stringWriter.GetStringBuilder().ToString());
+ }
+ }
+ }
+
+ private IError? GeneratePacketSerializer(IndentedTextWriter textWriter, Compilation compilation, RecordDeclarationSyntax packetClass)
+ {
+ var semanticModel = compilation.GetSemanticModel(packetClass.SyntaxTree);
+
+ var name = packetClass.Identifier.NormalizeWhitespace().ToFullString();
+ var @namespace = packetClass.GetPrefix();
+
+ var constructor = (ParameterListSyntax?)packetClass.ChildNodes()
+ .FirstOrDefault(x => x.IsKind(SyntaxKind.ParameterList));
+
+ if (constructor is null)
+ {
+ return new DiagnosticError(
+ "SG0001",
+ "Packet without constructor",
+ "The packet class {0} does not have any constructors to use for packet serializer.",
+ packetClass.SyntaxTree,
+ packetClass.FullSpan,
+ new List<object?>(new[]
+ {
+ packetClass.Identifier.NormalizeWhitespace().ToFullString()
+ })
+ );
+ }
+
+ var parameters = constructor.Parameters;
+ var orderedParameters = new List<ParameterInfo>();
+ int constructorIndex = 0;
+ foreach (var parameter in parameters)
+ {
+ var attributeLists = parameter.AttributeLists
+ .Where(x => x.Attributes.Any(x => x.Name.NormalizeWhitespace().ToFullString().StartsWith("Packet"))).ToList();
+ var attributes = attributeLists.FirstOrDefault()?.Attributes.Where(x => x.Name.NormalizeWhitespace().ToFullString().StartsWith("Packet"))
+ .ToList();
+
+ if (attributeLists.Count > 1 || (attributes is not null && attributes?.Count > 1))
+ {
+ return new DiagnosticError
+ (
+ "SG0002",
+ "Packet constructor parameter with multiple packet attributes",
+ "There are multiple PacketIndexAttributes on {0} parameter in class {1}. Only one may be specified.",
+ parameter.SyntaxTree,
+ parameter.FullSpan,
+ new List<object?>(new[]
+ {
+ parameter.Identifier.NormalizeWhitespace().ToFullString(),
+ name
+ })
+ );
+ }
+ else if (attributeLists.Count == 0 || attributes is null || attributes.Count < 1)
+ {
+ return new DiagnosticError(
+ "SG0003",
+ "Packet constructor parameter without packet attribute",
+ "Could not find PacketIndexAttribute on {0} parameter in class {1}. Parameters without PacketIndexAttribute aren't allowed.",
+ parameter.SyntaxTree,
+ parameter.FullSpan,
+ new List<object?>(new[]
+ {
+ parameter.Identifier.NormalizeWhitespace().ToFullString(),
+ name
+ })
+ );
+ }
+
+ var attribute = attributes.First();
+ var indexArg = attribute.ArgumentList!.Arguments[0];
+ var indexExp = indexArg.Expression;
+ var index = ushort.Parse(semanticModel.GetConstantValue(indexExp).ToString());
+ var namedArguments = new Dictionary<string, object?>();
+ var arguments = new List<object?>();
+
+ foreach (var argument in attribute.ArgumentList.Arguments)
+ {
+ var argumentName = argument.NameEquals?.Name.Identifier.NormalizeWhitespace().ToFullString();
+ var expression = argument.Expression;
+ var value = semanticModel.GetConstantValue(expression).Value;
+
+ if (argumentName is not null)
+ {
+ namedArguments.Add(argumentName, value);
+ }
+ else
+ {
+ arguments.Add(value);
+ }
+ }
+
+ orderedParameters.Add(new ParameterInfo
+ (
+ compilation,
+ parameter,
+ attribute,
+ arguments,
+ namedArguments,
+ parameter.Identifier.NormalizeWhitespace().ToFullString(),
+ constructorIndex,
+ index
+ )
+ );
+ constructorIndex++;
+ }
+
+ orderedParameters = orderedParameters.OrderBy(x => x.PacketIndex).ToList();
+ orderedParameters.Last().IsLast = true;
+
+ textWriter.WriteLine(@$"// <auto-generated/>
+#nullable enable
+#pragma warning disable 1591
+
+using {@namespace};
+using NosSmooth.Packets.Converters;
+using NosSmooth.Packets.Errors;
+using NosSmooth.Packets;
+using Remora.Results;
+
+namespace {@namespace}.Generated;
+
+public class {name}Converter : BaseTypeConverter<{name}>
+{{");
+ textWriter.Indent++;
+ textWriter.WriteLine($@"
+private readonly ITypeConverterRepository _typeConverterRepository;
+
+public {name}Converter(ITypeConverterRepository typeConverterRepository)
+{{
+ _typeConverterRepository = typeConverterRepository;
+}}
+
+/// <inheritdoc />
+public override Result Serialize({name}? obj, PacketStringBuilder builder)
+{{
+ if (obj is null)
+ {{
+ return new ArgumentNullError(nameof(obj));
+ }}
+");
+ textWriter.Indent++;
+ var serializerError = GenerateSerializer(textWriter, packetClass, orderedParameters);
+ if (serializerError is not null)
+ {
+ return serializerError;
+ }
+
+ textWriter.Indent--;
+ textWriter.WriteLine($@"
+}}
+
+/// <inheritdoc />
+public override Result<{name}?> Deserialize(PacketStringEnumerator stringEnumerator)
+{{
+");
+
+ textWriter.Indent++;
+ var deserializerError = GenerateDeserializer(textWriter, packetClass, orderedParameters);
+ if (deserializerError is not null)
+ {
+ return deserializerError;
+ }
+ textWriter.Indent--;
+
+ textWriter.WriteLine($@"
+ }}
+
+private IResultError? CheckDeserializationResult<T>(Result<T> result, string property, PacketStringEnumerator stringEnumerator, bool last = false)
+{{
+ if (!result.IsSuccess)
+ {{
+ return new PacketParameterSerializerError(this, property, result);
+ }}
+
+ if (!last && (stringEnumerator.IsOnLastToken() ?? false))
+ {{
+ return new PacketEndNotExpectedError(this, property);
+ }}
+
+ return null;
+}}
+}}");
+ return null;
+ }
+
+ private IError? GenerateSerializer(IndentedTextWriter textWriter, RecordDeclarationSyntax packetClass, List<ParameterInfo> parameters)
+ {
+ foreach (var parameter in parameters)
+ {
+ bool handled = false;
+ foreach (var generator in _generators)
+ {
+ if (generator.ShouldHandle(parameter.Attribute))
+ {
+ var result = generator.GenerateSerializerPart(textWriter, packetClass, parameter);
+ if (result is not null)
+ {
+ return result;
+ }
+
+ handled = true;
+ break;
+ }
+ }
+
+ if (!handled)
+ {
+ throw new InvalidOperationException($"Could not handle {packetClass.Identifier.NormalizeWhitespace().ToFullString()}.{parameter.Name}");
+ }
+ }
+
+ textWriter.WriteLine("return Result.FromSuccess();");
+ return null;
+ }
+
+ private IError? GenerateDeserializer(IndentedTextWriter textWriter, RecordDeclarationSyntax packetClass, List<ParameterInfo> parameters)
+ {
+ var lastIndex = parameters.FirstOrDefault()?.PacketIndex ?? 0;
+ bool skipped = false;
+ foreach (var parameter in parameters)
+ {
+ 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();");
+ textWriter.WriteLine($@"skipError = CheckDeserializationResult(result, ""None"", stringEnumerator, false);");
+ textWriter.WriteLine($@"if (skipError is not null) {{
+ return Result<{packetClass.Identifier.NormalizeWhitespace().ToFullString()}>.FromError(skipError, skipResult);
+}}");
+ }
+
+ bool handled = false;
+ foreach (var generator in _generators)
+ {
+ if (generator.ShouldHandle(parameter.Attribute))
+ {
+ var result = generator.GenerateDeserializerPart(textWriter, packetClass, parameter);
+ if (result is not null)
+ {
+ return result;
+ }
+
+ handled = true;
+ break;
+ }
+ }
+
+ if (!handled)
+ {
+ throw new InvalidOperationException($"Could not handle {packetClass.Identifier.NormalizeWhitespace().ToFullString()}.{parameter.Name}");
+ }
+ lastIndex = parameter.PacketIndex;
+ }
+
+ string parametersString = string.Join(", ", parameters.OrderBy(x => x.ConstructorIndex).Select(x => x.Name));
+ textWriter.WriteLine($"return new {packetClass.Identifier.NormalizeWhitespace().ToFullString()}({parametersString});");
+
+ return null;
+ }
+}<
\ No newline at end of file
A Core/NosSmooth.PacketSerializersGenerator/ParameterInfo.cs => Core/NosSmooth.PacketSerializersGenerator/ParameterInfo.cs +39 -0
@@ 0,0 1,39 @@
+//
+// ParameterInfo.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 Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+namespace NosSmooth.PacketSerializersGenerator;
+
+/// <summary>
+/// Information about a parameter of a packet constructor.
+/// </summary>
+/// <param name="Compilation"></param>
+/// <param name="Parameter"></param>
+/// <param name="Attribute"></param>
+/// <param name="IndexedAttributeArguments"></param>
+/// <param name="NamedAttributeArguments"></param>
+/// <param name="Name"></param>
+/// <param name="ConstructorIndex"></param>
+/// <param name="PacketIndex"></param>
+public record ParameterInfo
+(
+ Compilation Compilation,
+ ParameterSyntax Parameter,
+ AttributeSyntax Attribute,
+ IReadOnlyList<object?> IndexedAttributeArguments,
+ IReadOnlyDictionary<string, object?> NamedAttributeArguments,
+ string Name,
+ int ConstructorIndex,
+ int PacketIndex
+)
+{
+ /// <summary>
+ /// Gets or sets if this parameter is the last one.
+ /// </summary>
+ public bool IsLast { get; set; }
+}<
\ No newline at end of file