2026-05-28 10:45:35 +07:00
|
|
|
|
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";
|
|
|
|
|
|
|
2026-05-28 16:44:39 +07:00
|
|
|
|
private const string AttributeSource = @"namespace QWERTYkez.Mensura;
|
2026-05-28 10:45:35 +07:00
|
|
|
|
|
2026-05-29 16:45:24 +07:00
|
|
|
|
[System.AttributeUsage(System.AttributeTargets.Struct | System.AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
|
2026-05-28 10:45:35 +07:00
|
|
|
|
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<KeyValuePair<ClassData, ImmutableArray<Operation>>>(
|
|
|
|
|
|
(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<OperatorDeclarationSyntax>()
|
|
|
|
|
|
.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<OperatorDeclarationSyntax>()
|
|
|
|
|
|
.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<KeyValuePair<ClassData, ImmutableArray<Operation>>> 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;
|
|
|
|
|
|
}
|