//
// PacketConditionalIndexAttributeGenerator.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;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using NosSmooth.PacketSerializersGenerator.Data;
using NosSmooth.PacketSerializersGenerator.Errors;
using NosSmooth.PacketSerializersGenerator.Extensions;
namespace NosSmooth.PacketSerializersGenerator.AttributeGenerators;
///
public class PacketConditionalIndexAttributeGenerator : IParameterGenerator
{
private readonly InlineTypeConverterGenerator _inlineTypeConverterGenerators;
///
/// Initializes a new instance of the class.
///
/// The generator for types.
public PacketConditionalIndexAttributeGenerator(InlineTypeConverterGenerator inlineTypeConverterGenerators)
{
_inlineTypeConverterGenerators = inlineTypeConverterGenerators;
}
///
/// Gets the full name of the packet index attribute.
///
public static string PacketConditionalIndexAttributeFullName
=> "NosSmooth.PacketSerializer.Abstractions.Attributes.PacketConditionalIndexAttribute";
///
public bool ShouldHandle(ParameterInfo parameter)
=> parameter.Attributes.Any(x => x.FullName == PacketConditionalIndexAttributeFullName);
///
public IError? CheckParameter(PacketInfo packet, ParameterInfo parameter)
{
if (!parameter.Nullable)
{
return new DiagnosticError
(
"SGNull",
"Conditional parameters must be nullable",
"The parameter {0} in {1} has to be nullable, because it is conditional.",
parameter.Parameter.SyntaxTree,
parameter.Parameter.FullSpan,
new List(new[] { parameter.Name, packet.Name })
);
}
if (parameter.Attributes.Any(x => x.FullName != PacketConditionalIndexAttributeFullName))
{
return new DiagnosticError
(
"SGAttr",
"Packet constructor parameter with multiple packet attributes",
"Found multiple packet attributes of multiple types on parameter {0} in {1}. PacketConditionalIndexAttribute supports multiple attributes of the same type only.",
parameter.Parameter.SyntaxTree,
parameter.Parameter.FullSpan,
new List
(
new[]
{
parameter.Name,
packet.Name
}
)
);
}
// Check that all attributes have the same data. (where the same data need to be)
var firstAttribute = parameter.Attributes.First();
if (parameter.Attributes.Any
(
x =>
{
var index = x.GetIndexedValue(0);
if (index != parameter.PacketIndex)
{
return true;
}
foreach (var keyValue in x.NamedAttributeArguments)
{
if (!firstAttribute.NamedAttributeArguments.ContainsKey(keyValue.Key))
{
return true;
}
if (firstAttribute.NamedAttributeArguments[keyValue.Key] != keyValue.Value)
{
return true;
}
}
return false;
}
))
{
return new DiagnosticError
(
"SGAttr",
"Packet constructor parameter with multiple conflicting attribute data.",
"Found multiple packet attributes of multiple types on parameter {0} in {1} with conflicting data. Index, IsOptional, InnerSeparator, AfterSeparator all have to be the same for each attribute.",
parameter.Parameter.SyntaxTree,
parameter.Parameter.FullSpan,
new List
(
new[]
{
parameter.Name,
packet.Name
}
)
);
}
var mismatchedAttribute = parameter.Attributes.FirstOrDefault
(
x => x.IndexedAttributeArguments.Count < 4 ||
(x.IndexedAttributeArguments[3].IsArray &&
(
(x.IndexedAttributeArguments[3].Argument.Expression as ArrayCreationExpressionSyntax)
?.Initializer is null ||
(x.IndexedAttributeArguments[3].Argument.Expression as ArrayCreationExpressionSyntax)
?.Initializer?.Expressions.Count == 0
)
)
);
if (mismatchedAttribute is not null)
{
return new DiagnosticError
(
"SGAttr",
"Packet conditional attribute without matching values",
"Found PacketConditionalIndexAttribute without matchValues parameters set on {0} in {1}. At least one parameter has to be specified.",
mismatchedAttribute.Attribute.SyntaxTree,
mismatchedAttribute.Attribute.FullSpan,
new List
(
new[]
{
parameter.Name,
packet.Name
}
)
);
}
return ParameterChecker.CheckOptionalIsNullable(packet, parameter);
}
private string BuildAttributeIfPart(AttributeInfo attribute, string prefix)
{
var conditionParameterName = attribute.GetIndexedValue(1);
var negate = attribute.GetIndexedValue(2);
var values = attribute.GetParamsVisualValues(3);
if (conditionParameterName is null || values is null)
{
throw new ArgumentException();
}
var inner = string.Join
(" || ", values.Select(x => $"{prefix}{conditionParameterName.Trim('"')} == {x?.ToString() ?? "null"}"));
return (negate ? "!(" : string.Empty) + inner + (negate ? ")" : string.Empty);
}
private string BuildParameterIfStatement(ParameterInfo parameter, string prefix)
{
var ifInside = string.Empty;
foreach (var attribute in parameter.Attributes)
{
ifInside += BuildAttributeIfPart(attribute, prefix);
}
return $"if ({ifInside})";
}
///
public IError? GenerateSerializerPart(IndentedTextWriter textWriter, PacketInfo packetInfo)
{
bool pushedLevel = false;
var generator = new ConverterSerializationGenerator(textWriter);
var parameter = packetInfo.Parameters.Current;
var attributes = parameter.Attributes;
var attribute = attributes.First();
// begin conditional if
textWriter.WriteLine(BuildParameterIfStatement(parameter, "obj."));
textWriter.WriteLine("{");
textWriter.Indent++;
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)
{
generator.PushLevel((char)innerSeparator);
pushedLevel = true;
}
// serialize, check the error.
_inlineTypeConverterGenerators.Serialize(textWriter, packetInfo);
// pop inner separator level
if (pushedLevel)
{
generator.PopLevel();
}
// end optional if
if (parameter.IsOptional())
{
textWriter.Indent--;
textWriter.WriteLine("}");
}
// end conditional if
textWriter.Indent--;
textWriter.WriteLine("}");
return null;
}
///
public IError? GenerateDeserializerPart(IndentedTextWriter textWriter, PacketInfo packetInfo)
{
bool pushedLevel = false;
var generator = new ConverterDeserializationGenerator(textWriter);
var parameter = packetInfo.Parameters.Current;
var attribute = parameter.Attributes.First();
generator.DeclareLocalVariable(parameter);
// begin conditional if
textWriter.WriteLine(BuildParameterIfStatement(parameter, string.Empty));
textWriter.WriteLine("{");
textWriter.Indent++;
// add optional if
if (parameter.IsOptional())
{
generator.StartOptionalCheck(parameter, packetInfo.Name);
}
else
{
generator.ValidateNotLast(parameter.Name);
}
var afterSeparator = attribute.GetNamedValue("AfterSeparator", null);
if (afterSeparator is not null)
{
generator.SetAfterSeparatorOnce((char)afterSeparator);
}
var innerSeparator = attribute.GetNamedValue("InnerSeparator", null);
if (innerSeparator is not null)
{
generator.PushLevel((char)innerSeparator);
pushedLevel = true;
}
generator.DeserializeAndCheck(parameter, packetInfo, _inlineTypeConverterGenerators);
if (!parameter.Nullable)
{
generator.CheckNullError(parameter.GetNullableVariableName(), parameter.GetResultVariableName(), parameter.Name);
}
generator.AssignLocalVariable(parameter, false);
if (pushedLevel)
{
generator.ReadToLastToken();
generator.PopLevel();
}
// end is last token if body
if (parameter.IsOptional())
{
generator.EndOptionalCheck(parameter);
}
// end conditional if
textWriter.Indent--;
textWriter.WriteLine("}");
textWriter.WriteLine("else");
textWriter.WriteLine("{");
textWriter.Indent++;
textWriter.WriteLine($"{parameter.GetVariableName()} = null;");
textWriter.Indent--;
textWriter.WriteLine("}");
return null;
}
}