2026-06-09 16:45:22 +07:00
|
|
|
|
namespace G
|
2026-06-05 12:13:35 +07:00
|
|
|
|
{
|
2026-06-07 15:54:53 +07:00
|
|
|
|
[Generator(LanguageNames.CSharp)]
|
|
|
|
|
|
public class OperatorsGenerator : IIncrementalGenerator
|
2026-06-05 12:13:35 +07:00
|
|
|
|
{
|
2026-06-07 15:54:53 +07:00
|
|
|
|
private const string AttributeSource = @"// <auto-generated />
|
|
|
|
|
|
using System;
|
2026-06-05 12:13:35 +07:00
|
|
|
|
|
2026-06-07 15:54:53 +07:00
|
|
|
|
namespace QWERTYkez.Mensura
|
|
|
|
|
|
{
|
|
|
|
|
|
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
|
2026-06-09 16:45:22 +07:00
|
|
|
|
internal sealed class OperatorsGeneratorAttribute : Attribute
|
2026-06-05 12:13:35 +07:00
|
|
|
|
{
|
2026-06-07 15:54:53 +07:00
|
|
|
|
public string CoeffName { get; }
|
|
|
|
|
|
|
|
|
|
|
|
public OperatorsGeneratorAttribute()
|
|
|
|
|
|
{
|
|
|
|
|
|
}
|
2026-06-05 12:13:35 +07:00
|
|
|
|
|
2026-06-07 15:54:53 +07:00
|
|
|
|
public OperatorsGeneratorAttribute(string coeffName)
|
2026-06-05 12:13:35 +07:00
|
|
|
|
{
|
2026-06-07 15:54:53 +07:00
|
|
|
|
CoeffName = coeffName;
|
2026-06-05 12:13:35 +07:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-06-07 15:54:53 +07:00
|
|
|
|
}";
|
2026-06-05 12:13:35 +07:00
|
|
|
|
|
2026-06-07 15:54:53 +07:00
|
|
|
|
public void Initialize(IncrementalGeneratorInitializationContext context)
|
2026-06-05 12:13:35 +07:00
|
|
|
|
{
|
2026-06-07 15:54:53 +07:00
|
|
|
|
// 1. Внедряем атрибут
|
|
|
|
|
|
context.RegisterPostInitializationOutput(static postInitializationContext =>
|
|
|
|
|
|
{
|
|
|
|
|
|
postInitializationContext.AddSource(
|
|
|
|
|
|
".OperatorsGeneratorAttribute.g.cs",
|
|
|
|
|
|
SourceText.From(AttributeSource, Encoding.UTF8));
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 2. Ищем методы-операторы, помеченные атрибутом
|
|
|
|
|
|
IncrementalValuesProvider<OperatorInfo> operatorDeclarations = context.SyntaxProvider
|
|
|
|
|
|
.ForAttributeWithMetadataName(
|
|
|
|
|
|
fullyQualifiedMetadataName: "QWERTYkez.Mensura.OperatorsGeneratorAttribute",
|
|
|
|
|
|
predicate: static (node, _) => node is OperatorDeclarationSyntax,
|
|
|
|
|
|
transform: static (ctx, _) => GetSemanticTargetForGeneration(ctx))
|
|
|
|
|
|
.Where(static m => m is not null)!;
|
|
|
|
|
|
|
|
|
|
|
|
// 3. Выполняем генерацию кода
|
|
|
|
|
|
context.RegisterSourceOutput(operatorDeclarations, static (spc, opInfo) => Execute(spc, opInfo));
|
2026-06-05 12:13:35 +07:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-07 15:54:53 +07:00
|
|
|
|
private static OperatorInfo GetSemanticTargetForGeneration(GeneratorAttributeSyntaxContext context)
|
|
|
|
|
|
{
|
|
|
|
|
|
var opDeclaration = (OperatorDeclarationSyntax)context.TargetNode;
|
|
|
|
|
|
|
|
|
|
|
|
if (opDeclaration.ParameterList.Parameters.Count < 2) return null;
|
2026-06-05 12:13:35 +07:00
|
|
|
|
|
2026-06-07 15:54:53 +07:00
|
|
|
|
if (context.SemanticModel.GetDeclaredSymbol(opDeclaration) is not IMethodSymbol symbol) return null;
|
2026-06-05 12:13:35 +07:00
|
|
|
|
|
2026-06-07 15:54:53 +07:00
|
|
|
|
var namespaceName = symbol.ContainingNamespace.ToDisplayString();
|
|
|
|
|
|
|
|
|
|
|
|
var leftType = opDeclaration.ParameterList.Parameters[0].Type?.ToString() ?? "";
|
|
|
|
|
|
var rightType = opDeclaration.ParameterList.Parameters[1].Type?.ToString() ?? "";
|
|
|
|
|
|
var returnType = opDeclaration.ReturnType.ToString();
|
2026-06-05 12:13:35 +07:00
|
|
|
|
|
2026-06-07 15:54:53 +07:00
|
|
|
|
var isMultiplication = opDeclaration.OperatorToken.IsKind(SyntaxKind.AsteriskToken);
|
|
|
|
|
|
var isDivision = opDeclaration.OperatorToken.IsKind(SyntaxKind.SlashToken);
|
2026-06-05 12:13:35 +07:00
|
|
|
|
|
2026-06-07 15:54:53 +07:00
|
|
|
|
if (!isMultiplication && !isDivision) return null;
|
|
|
|
|
|
|
|
|
|
|
|
// АВТОМАТИЧЕСКИЙ ПОИСК: Ищем содержащий тип (структуру/рекорд) по синтаксическому дереву
|
|
|
|
|
|
string containingTypeName = leftType; // Фолбэк по умолчанию
|
|
|
|
|
|
var parentTypeDeclaration = opDeclaration.Ancestors()
|
|
|
|
|
|
.OfType<TypeDeclarationSyntax>()
|
|
|
|
|
|
.FirstOrDefault();
|
|
|
|
|
|
|
|
|
|
|
|
if (parentTypeDeclaration != null)
|
2026-06-05 12:13:35 +07:00
|
|
|
|
{
|
2026-06-07 15:54:53 +07:00
|
|
|
|
containingTypeName = parentTypeDeclaration.Identifier.ToString();
|
2026-06-05 12:13:35 +07:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-07 15:54:53 +07:00
|
|
|
|
string coeffName = null;
|
|
|
|
|
|
string coeffType = containingTypeName; // По умолчанию считаем, что коэффициент в текущем классе
|
|
|
|
|
|
|
|
|
|
|
|
// Извлекаем аргумент атрибута
|
|
|
|
|
|
var attribute = opDeclaration.AttributeLists
|
|
|
|
|
|
.SelectMany(al => al.Attributes)
|
|
|
|
|
|
.FirstOrDefault(a => a.Name.ToString().EndsWith("OperatorsGenerator") || a.Name.ToString() == "OperatorsGenerator");
|
|
|
|
|
|
|
|
|
|
|
|
if (attribute?.ArgumentList != null && attribute.ArgumentList.Arguments.Count > 0)
|
2026-06-05 12:13:35 +07:00
|
|
|
|
{
|
2026-06-07 15:54:53 +07:00
|
|
|
|
var expr = attribute.ArgumentList.Arguments[0].Expression;
|
|
|
|
|
|
|
|
|
|
|
|
// Проверяем синтаксис nameof(...)
|
|
|
|
|
|
if (expr is InvocationExpressionSyntax invocation &&
|
|
|
|
|
|
invocation.Expression.ToString() == "nameof" &&
|
|
|
|
|
|
invocation.ArgumentList.Arguments.Count > 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
var nameofArg = invocation.ArgumentList.Arguments[0].Expression;
|
|
|
|
|
|
|
|
|
|
|
|
// Если все-таки написали явно nameof(Type.Property)
|
|
|
|
|
|
if (nameofArg is MemberAccessExpressionSyntax memberAccess)
|
|
|
|
|
|
{
|
|
|
|
|
|
coeffType = memberAccess.Expression.ToString();
|
|
|
|
|
|
coeffName = memberAccess.Name.ToString();
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
coeffName = nameofArg.ToString();
|
|
|
|
|
|
// coeffType остается равным содержащему классу containingTypeName
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
coeffName = expr.ToString().Trim('"');
|
|
|
|
|
|
// coeffType остается равным содержащему классу containingTypeName
|
|
|
|
|
|
}
|
2026-06-05 12:13:35 +07:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-07 15:54:53 +07:00
|
|
|
|
return new OperatorInfo
|
|
|
|
|
|
{
|
|
|
|
|
|
Namespace = namespaceName,
|
|
|
|
|
|
LeftType = leftType,
|
|
|
|
|
|
RightType = rightType,
|
|
|
|
|
|
ReturnType = returnType,
|
|
|
|
|
|
IsMultiplication = isMultiplication,
|
|
|
|
|
|
IsDivision = isDivision,
|
|
|
|
|
|
CoeffName = coeffName,
|
|
|
|
|
|
CoeffType = coeffType,
|
|
|
|
|
|
HasCoeff = !string.IsNullOrWhiteSpace(coeffName)
|
|
|
|
|
|
};
|
2026-06-05 12:13:35 +07:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-07 15:54:53 +07:00
|
|
|
|
private static void Execute(SourceProductionContext context, OperatorInfo info)
|
|
|
|
|
|
{
|
|
|
|
|
|
var opType = info.IsMultiplication ? "Mul" : "Div";
|
|
|
|
|
|
var coeffSuffix = info.HasCoeff ? ".Coeff" : "";
|
|
|
|
|
|
var hintName = $"Op.{info.LeftType}.{opType}.{info.RightType}{coeffSuffix}.g.cs";
|
2026-06-05 12:13:35 +07:00
|
|
|
|
|
2026-06-07 15:54:53 +07:00
|
|
|
|
var source = info.IsMultiplication
|
|
|
|
|
|
? GenerateMultiplicationSource(info)
|
|
|
|
|
|
: GenerateDivisionSource(info);
|
|
|
|
|
|
|
|
|
|
|
|
context.AddSource(hintName, SourceText.From(source, Encoding.UTF8));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static string GenerateMultiplicationSource(OperatorInfo info)
|
|
|
|
|
|
{
|
|
|
|
|
|
return $@"// <auto-generated />
|
|
|
|
|
|
using System;
|
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
|
|
|
|
|
|
namespace {info.Namespace};
|
|
|
|
|
|
|
|
|
|
|
|
public readonly partial record struct {info.LeftType}
|
|
|
|
|
|
{{
|
2026-06-09 16:45:22 +07:00
|
|
|
|
public static {info.ReturnType}[] operator *({info.LeftType} left, {info.RightType}[] right) => {(info.HasCoeff ? $"({info.CoeffType}.{info.CoeffName} * left._Value)" : "left._Value")}.Mul<{info.RightType}, {info.ReturnType}>(right);
|
|
|
|
|
|
public static List<{info.ReturnType}> operator *({info.LeftType} left, List<{info.RightType}> right) => {(info.HasCoeff ? $"({info.CoeffType}.{info.CoeffName} * left._Value)" : "left._Value")}.Mul<{info.RightType}, {info.ReturnType}>(right);
|
|
|
|
|
|
public static IEnumerable<{info.ReturnType}> operator *({info.LeftType} left, IEnumerable<{info.RightType}> right) => {(info.HasCoeff ? $"({info.CoeffType}.{info.CoeffName} * left._Value)" : "left._Value")}.Mul<{info.RightType}, {info.ReturnType}>(right);
|
2026-06-07 15:54:53 +07:00
|
|
|
|
}}
|
|
|
|
|
|
|
|
|
|
|
|
public readonly partial record struct {info.RightType}
|
|
|
|
|
|
{{
|
2026-06-09 16:45:22 +07:00
|
|
|
|
public static {info.ReturnType}[] operator *({info.LeftType}[] left, {info.RightType} right) => {(info.HasCoeff ? $"({info.CoeffType}.{info.CoeffName} * right._Value)" : "right._Value")}.Mul<{info.LeftType}, {info.ReturnType}>(left);
|
|
|
|
|
|
public static List<{info.ReturnType}> operator *(List<{info.LeftType}> left, {info.RightType} right) => {(info.HasCoeff ? $"({info.CoeffType}.{info.CoeffName} * right._Value)" : "right._Value")}.Mul<{info.LeftType}, {info.ReturnType}>(left);
|
|
|
|
|
|
public static IEnumerable<{info.ReturnType}> operator *(IEnumerable<{info.LeftType}> left, {info.RightType} right) => {(info.HasCoeff ? $"({info.CoeffType}.{info.CoeffName} * right._Value)" : "right._Value")}.Mul<{info.LeftType}, {info.ReturnType}>(left);
|
2026-06-07 15:54:53 +07:00
|
|
|
|
}}";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static string GenerateDivisionSource(OperatorInfo info)
|
|
|
|
|
|
{
|
|
|
|
|
|
return $@"// <auto-generated />
|
|
|
|
|
|
using System;
|
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
|
|
|
|
|
|
namespace {info.Namespace};
|
|
|
|
|
|
|
|
|
|
|
|
public readonly partial record struct {info.LeftType}
|
|
|
|
|
|
{{
|
2026-06-09 16:45:22 +07:00
|
|
|
|
public static {info.ReturnType}[] operator /({info.LeftType} left, {info.RightType}[] right) => {(info.HasCoeff ? $"({info.CoeffType}.{info.CoeffName} * left._Value)" : "left._Value")}.Div<{info.RightType}, {info.ReturnType}>(right);
|
|
|
|
|
|
public static List<{info.ReturnType}> operator /({info.LeftType} left, List<{info.RightType}> right) => {(info.HasCoeff ? $"({info.CoeffType}.{info.CoeffName} * left._Value)" : "left._Value")}.Div<{info.RightType}, {info.ReturnType}>(right);
|
|
|
|
|
|
public static IEnumerable<{info.ReturnType}> operator /({info.LeftType} left, IEnumerable<{info.RightType}> right) => {(info.HasCoeff ? $"({info.CoeffType}.{info.CoeffName} * left._Value)" : "left._Value")}.Div<{info.RightType}, {info.ReturnType}>(right);
|
2026-06-07 15:54:53 +07:00
|
|
|
|
}}
|
|
|
|
|
|
|
|
|
|
|
|
public readonly partial record struct {info.RightType}
|
|
|
|
|
|
{{
|
2026-06-09 16:45:22 +07:00
|
|
|
|
public static {info.ReturnType}[] operator /({info.LeftType}[] left, {info.RightType} right) => {(info.HasCoeff ? $"left.Mul<{info.LeftType}, {info.ReturnType}>({info.CoeffType}.{info.CoeffName} / right._Value)" : $"left.Div<{info.LeftType}, {info.ReturnType}>(right._Value)")};
|
|
|
|
|
|
public static List<{info.ReturnType}> operator /(List<{info.LeftType}> left, {info.RightType} right) => {(info.HasCoeff ? $"left.Mul<{info.LeftType}, {info.ReturnType}>({info.CoeffType}.{info.CoeffName} / right._Value)" : $"left.Div<{info.LeftType}, {info.ReturnType}>(right._Value)")};
|
|
|
|
|
|
public static IEnumerable<{info.ReturnType}> operator /(IEnumerable<{info.LeftType}> left, {info.RightType} right) => {(info.HasCoeff ? $"left.Mul<{info.LeftType}, {info.ReturnType}>({info.CoeffType}.{info.CoeffName} / right._Value)" : $"left.Div<{info.LeftType}, {info.ReturnType}>(right._Value)")};
|
2026-06-07 15:54:53 +07:00
|
|
|
|
}}";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private class OperatorInfo
|
|
|
|
|
|
{
|
|
|
|
|
|
public string Namespace { get; set; } = string.Empty;
|
|
|
|
|
|
public string LeftType { get; set; } = string.Empty;
|
|
|
|
|
|
public string RightType { get; set; } = string.Empty;
|
|
|
|
|
|
public string ReturnType { get; set; } = string.Empty;
|
|
|
|
|
|
public bool IsMultiplication { get; set; }
|
|
|
|
|
|
public bool IsDivision { get; set; }
|
|
|
|
|
|
public string CoeffName { get; set; }
|
|
|
|
|
|
public string CoeffType { get; set; }
|
|
|
|
|
|
public bool HasCoeff { get; set; } }
|
2026-06-05 12:13:35 +07:00
|
|
|
|
}
|
|
|
|
|
|
}
|