using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Text; namespace QWERTYkez.Mensura.Generator; [Generator(LanguageNames.CSharp)] internal class CollectionsOperatorsGenerator : IIncrementalGenerator { private const string AttributeShortName = "CollectionsOperatorsGenerator"; private const string AttributeFullName = AttributeShortName + "Attribute"; private const string AttributeSource = @"namespace QWERTYkez.Mensura; [System.AttributeUsage(System.AttributeTargets.Struct | System.AttributeTargets.Method, AllowMultiple = false, Inherited = false)] internal sealed class CollectionsOperatorsGeneratorAttribute : System.Attribute { }"; public void Initialize(IncrementalGeneratorInitializationContext context) { context.RegisterPostInitializationOutput(context => context.AddSource( $"{AttributeFullName}.g", SourceText.From(AttributeSource, Encoding.UTF8))); var operatorsPipeline = context.SyntaxProvider.CreateSyntaxProvider>>( (node, _) => { if (node is ClassDeclarationSyntax cds) { SyntaxNode sn = cds; while (sn.Parent is not null && sn.Parent is not FileScopedNamespaceDeclarationSyntax) { sn = sn.Parent; } if (sn.Parent is FileScopedNamespaceDeclarationSyntax && cds.TypeParameterList is null && ((cds.Members.OfType() .Where(m => m.AttributeLists.SelectMany(al => al.Attributes) .Any(a => a.Name.GetText().ToString().Contains(AttributeShortName))) .Any()) || (cds.Modifiers.Any(m => m.Text == "partial") && cds.AttributeLists .SelectMany(al => al.Attributes) .Any(a => a.Name .GetText() .ToString() .Contains(AttributeShortName))))) { return true; } } return false; }, (syntax, _) => { string nameSpace; var cds = (ClassDeclarationSyntax)syntax.Node; { SyntaxNode sn = cds; while (sn.Parent is not FileScopedNamespaceDeclarationSyntax) { sn = sn.Parent!; } var nds = (FileScopedNamespaceDeclarationSyntax)sn.Parent; nameSpace = nds.Name.ToString(); } var Res = new StringBuilder(); { Res.Append(cds.Modifiers); Res.Append(" class "); Res.Append(cds.Identifier.Text); Res.Append(" "); Res.Append(cds.BaseList); Res.Append(" "); Res.Append(cds.ConstraintClauses); } var operators = cds.Members.OfType() .Where(m => m.AttributeLists .SelectMany(al => al.Attributes) .Any(a => a.Name.GetText().ToString().Contains(AttributeShortName))) .Where(mb => mb.ParameterList.Parameters.Count == 2) .Select(mb => new Operation() { ReturnType = mb.ReturnType.ToString(), OperatorToken = mb.OperatorToken.Text, TypeA = mb.ParameterList.Parameters[0].Type!.ToString(), TypeB = mb.ParameterList.Parameters[1].Type!.ToString(), }); return new(new(nameSpace, cds.Identifier.Text, Res.ToString()), [.. operators]); }) .Collect(); context.RegisterSourceOutput(operatorsPipeline, GenerateOperators); } readonly static string[] CollectionTypes = ["MetricArray", "MetricList", "MetricObservableCollection"]; static void GenerateOperators(SourceProductionContext context, ImmutableArray>> pairs) { foreach (var ng in pairs.GroupBy(c => c.Key.NameSpace)) { StringBuilder document = new("namespace "); document.Append(ng.Key); document.Append(";"); var classes = ng.ToList().Select(c => (ClassData?)c.Key).ToList(); var operations = ng.ToList().SelectMany(c => c.Value).ToList(); var multiplications = operations.Where(op => op.OperatorToken == "*").ToList(); var divisions = operations.Where(op => op.OperatorToken == "/").ToList(); foreach (var ops in multiplications.GroupBy(op => op.TypeA)) { var Class = classes.FirstOrDefault(cl => cl!.Value.ClassName == ops.Key); if (Class is not null) { document.AppendLine(); document.AppendLine(); document.AppendLine(Class.Value.ClassHeader); document.AppendLine("{"); foreach (var op in ops) foreach (var ct in CollectionTypes) { document.AppendLine(@$" public static {ct}<{op.ReturnType}> operator *({op.TypeA} left, {ct}<{op.TypeB}> right) => right.MetricSelect(UU => left * UU); public static {ct}<{op.ReturnType}> operator *({ct}<{op.TypeB}> left, {op.TypeA} right) => left.MetricSelect(UU => UU * right); "); } document.Append("}"); } else { context.ReportDiagnostic(Diagnostic.Create(new( "MSG0001", "Need a class with an attribute", $"It is necessary to have a empty partial class \"{ops.Key}\" with the attribute \"{AttributeShortName}\"", "category", DiagnosticSeverity.Error, true), null, ops.Key)); } } foreach (var ops in divisions.GroupBy(op => op.TypeA)) { var Class = classes.FirstOrDefault(cl => cl!.Value.ClassName == ops.Key); if (Class is not null) { document.AppendLine(); document.AppendLine(); document.AppendLine(Class.Value.ClassHeader); document.AppendLine("{"); foreach (var op in ops) foreach (var ct in CollectionTypes) { document.AppendLine(@$" public static {ct}<{op.ReturnType}> operator /({op.TypeA} left, {ct}<{op.TypeB}> right) => right.MetricSelect(UU => left / UU); "); } document.Append("}"); } else { context.ReportDiagnostic(Diagnostic.Create(new( "MSG0001", "Need a class with an attribute", $"It is necessary to have a empty partial class \"{ops.Key}\" with the attribute \"{AttributeShortName}\"", "category", DiagnosticSeverity.Error, true), null, ops.Key)); } } foreach (var ops in divisions.GroupBy(op => op.TypeB)) { var Class = classes.FirstOrDefault(cl => cl!.Value.ClassName == ops.Key); if (Class is not null) { document.AppendLine(); document.AppendLine(); document.AppendLine(Class.Value.ClassHeader); document.AppendLine("{"); foreach (var op in ops) foreach (var ct in CollectionTypes) { document.AppendLine(@$" public static {ct}<{op.ReturnType}> operator /({ct}<{op.TypeA}> left, {op.TypeB} right) => left.MetricSelect(UU => UU / right); "); } document.Append("}"); } else { context.ReportDiagnostic(Diagnostic.Create(new( "MSG0001", "Need a class with an attribute", $"It is necessary to have a empty partial class \"{ops.Key}\" with the attribute \"{AttributeShortName}\"", "category", DiagnosticSeverity.Error, true), null, ops.Key)); } } context.AddSource($"operations.{ng.Key}.g", document.ToString()); } } } public struct ClassData(string nameSpace, string className, string classHeader) { public string NameSpace = nameSpace; public string ClassName = className; public string ClassHeader = classHeader; } public struct Operation(string operatorToken, string typeA, string typeB, string returnType) { public string ReturnType = returnType; public string OperatorToken = operatorToken; public string TypeA = typeA; public string TypeB = typeB; }