Files
QWERTYkez.Mensura/QWERTYkez.Mensura.Generator/UnitOperatorsGenerator.cs
2026-05-28 10:45:35 +07:00

397 lines
18 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
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)]
public class UnitOperatorsGenerator : IIncrementalGenerator
{
public void Initialize(IncrementalGeneratorInitializationContext context)
{
// Регистрируем сам атрибут для использования в коде
context.RegisterPostInitializationOutput(ctx =>
{
ctx.AddSource("UnitOperatorsGeneratorAttribute.g.cs",
SourceText.From(@"namespace MetricSystem.Generator
{
[System.AttributeUsage(System.AttributeTargets.Struct | System.AttributeTargets.RecordStruct, AllowMultiple = false)]
public sealed class UnitOperatorsGeneratorAttribute : System.Attribute { }
}", Encoding.UTF8));
});
// Находим все record struct с атрибутом
var unitStructs = context.SyntaxProvider
.CreateSyntaxProvider(
predicate: static (node, _) => IsCandidate(node),
transform: static (ctx, _) => GetUnitInfo(ctx))
.Where(info => info != null)
.Select((info, _) => info!.Value)
.Collect();
context.RegisterSourceOutput(unitStructs, Execute);
}
private static bool IsCandidate(SyntaxNode node)
{
return node is RecordDeclarationSyntax record &&
record.Modifiers.Any(SyntaxKind.ReadOnlyKeyword) &&
record.Modifiers.Any(SyntaxKind.PartialKeyword) &&
record.Keyword.IsKind(SyntaxKind.RecordKeyword) &&
record.ClassOrStructKeyword.IsKind(SyntaxKind.StructKeyword);
}
private static UnitInfo? GetUnitInfo(GeneratorSyntaxContext context)
{
var record = (RecordDeclarationSyntax)context.Node;
var semanticModel = context.SemanticModel;
if (semanticModel.GetDeclaredSymbol(record) is not INamedTypeSymbol typeSymbol) return null;
// Проверяем наличие атрибута [UnitOperatorsGenerator]
bool hasAttribute = typeSymbol.GetAttributes()
.Any(attr => attr.AttributeClass?.Name == "UnitOperatorsGeneratorAttribute");
if (!hasAttribute) return null;
string ns = typeSymbol.ContainingNamespace?.ToString();
if (string.IsNullOrEmpty(ns)) return null;
string typeName = typeSymbol.Name;
// Собираем имена уже существующих членов, чтобы не генерировать дубликаты
var existingMemberNames = new HashSet<string>(
typeSymbol.GetMembers()
.Where(m => m.Kind == SymbolKind.Method || m.Kind == SymbolKind.Property || m.Kind == SymbolKind.Field)
.Select(m => m.Name));
var existingOperatorNames = new HashSet<string>(
typeSymbol.GetMembers()
.Where(m => m is IMethodSymbol method && method.MethodKind == MethodKind.UserDefinedOperator)
.Select(m => m.Name));
return new UnitInfo(ns, typeName, existingMemberNames, existingOperatorNames);
}
private static void Execute(SourceProductionContext context, ImmutableArray<UnitInfo> units)
{
// Группируем по полному имени типа, чтобы избежать дублирования hintName
var distinctUnits = units
.GroupBy(u => $"{u.Namespace}.{u.TypeName}")
.Select(g => g.First())
.ToImmutableArray();
foreach (var unit in distinctUnits)
{
string code = GenerateFullCode(unit);
string hintName = $"{unit.Namespace}.{unit.TypeName}.Generated.cs"
.Replace('<', '_')
.Replace('>', '_')
.Replace('[', '_')
.Replace(']', '_')
.Replace(' ', '_');
context.AddSource(hintName, SourceText.From(code, Encoding.UTF8));
}
}
private static string GenerateFullCode(UnitInfo info)
{
string t = info.TypeName;
var sb = new StringBuilder();
sb.AppendLine("// <auto-generated />");
sb.AppendLine("using System.Globalization;");
sb.AppendLine("using System.Runtime.Serialization;");
sb.AppendLine("using System.Numerics;");
sb.AppendLine();
sb.AppendLine($"namespace {info.Namespace};");
sb.AppendLine();
sb.AppendLine($"[System.Text.Json.Serialization.JsonConverter(typeof({t}Converter))]");
sb.AppendLine($"public readonly partial record struct {t}");
sb.AppendLine("{");
// 1. Поле и конструктор
sb.AppendLine($" [System.Text.Json.Serialization.JsonInclude, DataMember, System.Text.Json.Serialization.JsonPropertyName(\"v\")]");
sb.AppendLine($" internal double Value {{ get => _Value; init => _Value = value; }}");
sb.AppendLine($" internal readonly double _Value;");
sb.AppendLine($" internal {t}(double value) => _Value = value;");
sb.AppendLine();
// 2. Базовые методы
if (!info.ExistingMemberNames.Contains("GetHashCode"))
sb.AppendLine($" public override int GetHashCode() => _Value.GetHashCode();");
if (!info.ExistingMemberNames.Contains("CompareTo"))
{
sb.AppendLine($" public int CompareTo({t}? other) => _Value.CompareTo(other is null ? 0d : other._Value);");
sb.AppendLine($" public int CompareTo({t} other) => _Value.CompareTo(other._Value);");
}
if (!info.ExistingMemberNames.Contains("Equals"))
sb.AppendLine($" public bool Equals({t}? other) => _Value.Equals(other?._Value);");
sb.AppendLine();
// 3. Свойства IsPositive и т.д.
if (!info.ExistingMemberNames.Contains("IsPositive"))
sb.AppendLine($" [System.Text.Json.Serialization.JsonIgnore, IgnoreDataMember] public bool IsPositive => _Value >= 0;");
if (!info.ExistingMemberNames.Contains("IsGreaterThanZero"))
sb.AppendLine($" [System.Text.Json.Serialization.JsonIgnore, IgnoreDataMember] public bool IsGreaterThanZero => _Value > 0;");
if (!info.ExistingMemberNames.Contains("IsNegative"))
sb.AppendLine($" [System.Text.Json.Serialization.JsonIgnore, IgnoreDataMember] public bool IsNegative => _Value < 0;");
if (!info.ExistingMemberNames.Contains("IsZero"))
sb.AppendLine($" [System.Text.Json.Serialization.JsonIgnore, IgnoreDataMember] public bool IsZero => _Value == 0;");
sb.AppendLine();
// 4. Статические свойства
if (!info.ExistingMemberNames.Contains("Zero"))
sb.AppendLine($" public static {t} Zero => new(0d);");
if (!info.ExistingMemberNames.Contains("Min"))
sb.AppendLine($" public static {t} Min => new(double.MinValue);");
if (!info.ExistingMemberNames.Contains("Max"))
sb.AppendLine($" public static {t} Max => new(double.MaxValue);");
if (!info.ExistingMemberNames.Contains("NegativeInfinity"))
sb.AppendLine($" public static {t} NegativeInfinity => new(double.NegativeInfinity);");
if (!info.ExistingMemberNames.Contains("PositiveInfinity"))
sb.AppendLine($" public static {t} PositiveInfinity => new(double.PositiveInfinity);");
sb.AppendLine();
// 5. Операторы сравнения (с поддержкой null)
if (!info.ExistingOperatorNames.Contains("op_Equality"))
sb.AppendLine($" public static bool operator ==({t}? left, {t}? right) => (left is null ? 0d : left._Value) == (right is null ? 0d : right._Value);");
if (!info.ExistingOperatorNames.Contains("op_Inequality"))
sb.AppendLine($" public static bool operator !=({t}? left, {t}? right) => (left is null ? 0d : left._Value) != (right is null ? 0d : right._Value);");
if (!info.ExistingOperatorNames.Contains("op_LessThan"))
sb.AppendLine($" public static bool operator <({t}? left, {t}? right) => (left is null ? 0d : left._Value) < (right is null ? 0d : right._Value);");
if (!info.ExistingOperatorNames.Contains("op_LessThanOrEqual"))
sb.AppendLine($" public static bool operator <=({t}? left, {t}? right) => (left is null ? 0d : left._Value) <= (right is null ? 0d : right._Value);");
if (!info.ExistingOperatorNames.Contains("op_GreaterThan"))
sb.AppendLine($" public static bool operator >({t}? left, {t}? right) => (left is null ? 0d : left._Value) > (right is null ? 0d : right._Value);");
if (!info.ExistingOperatorNames.Contains("op_GreaterThanOrEqual"))
sb.AppendLine($" public static bool operator >=({t}? left, {t}? right) => (left is null ? 0d : left._Value) >= (right is null ? 0d : right._Value);");
sb.AppendLine();
// 6. Унарные операторы
if (!info.ExistingOperatorNames.Contains("op_UnaryPlus"))
sb.AppendLine($" public static {t} operator +({t} x) => x;");
if (!info.ExistingOperatorNames.Contains("op_UnaryNegation"))
sb.AppendLine($" public static {t} operator -({t} x) => new(-x._Value);");
sb.AppendLine();
// 7. Бинарные +, - с тем же типом
if (!info.ExistingOperatorNames.Contains("op_Addition"))
sb.AppendLine($" public static {t} operator +({t} a, {t} b) => new(a._Value + b._Value);");
if (!info.ExistingOperatorNames.Contains("op_Subtraction"))
sb.AppendLine($" public static {t} operator -({t} a, {t} b) => new(a._Value - b._Value);");
sb.AppendLine();
// 8. Умножение на числа (все числовые типы)
var numericTypes = new[] { "double", "double?", "byte", "byte?", "sbyte", "sbyte?",
"short", "short?", "ushort", "ushort?", "int", "int?", "uint", "uint?",
"long", "long?", "ulong", "ulong?", "float", "float?", "decimal", "decimal?" };
if (!info.ExistingOperatorNames.Contains("op_Multiply"))
{
foreach (var nt in numericTypes)
{
bool isNullable = nt.EndsWith("?");
string baseType = isNullable ? nt.TrimEnd('?') : nt;
if (baseType == "decimal")
{
string conv = isNullable ? "(double)(b ?? 0m)" : "(double)b";
sb.AppendLine($" public static {t} operator *({t} a, {nt} b) => new(a._Value * {conv});");
sb.AppendLine($" public static {t} operator *({nt} a, {t} b) => new({conv} * b._Value);");
}
else if (isNullable)
{
sb.AppendLine($" public static {t} operator *({t} a, {nt} b) => new(a._Value * (b ?? 0d));");
sb.AppendLine($" public static {t} operator *({nt} a, {t} b) => new((a ?? 0d) * b._Value);");
}
else
{
sb.AppendLine($" public static {t} operator *({t} a, {nt} b) => new(a._Value * b);");
sb.AppendLine($" public static {t} operator *({nt} a, {t} b) => new(a * b._Value);");
}
}
}
// 9. Деление на число
if (!info.ExistingOperatorNames.Contains("op_Division"))
{
foreach (var nt in numericTypes)
{
bool isNullable = nt.EndsWith("?");
string baseType = isNullable ? nt.TrimEnd('?') : nt;
if (baseType == "decimal")
{
string conv = isNullable ? "(double)(b ?? 0m)" : "(double)b";
sb.AppendLine($" public static {t} operator /({t} a, {nt} b) => new(a._Value / {conv});");
}
else if (isNullable)
{
sb.AppendLine($" public static {t} operator /({t} a, {nt} b) => new(a._Value / (b ?? 0d));");
}
else
{
sb.AppendLine($" public static {t} operator /({t} a, {nt} b) => new(a._Value / b);");
}
}
}
// 10. Деление двух метрик (возвращает double)
if (!info.ExistingOperatorNames.Contains("op_Division"))
sb.AppendLine($" public static double operator /({t} a, {t} b) => a._Value / b._Value;");
sb.AppendLine();
// 11. Методы SetArray / SetList
GenerateArrayAndListMethods(sb, t);
sb.AppendLine("}");
// 12. Конвертер JSON
GenerateJsonConverter(sb, t);
return sb.ToString();
}
private static void GenerateArrayAndListMethods(StringBuilder sb, string t)
{
// SetArray
sb.AppendLine($@" /// <summary>With Multiply</summary>
public {t}[] SetArray(IEnumerable<double> nums)
{{
if (nums is not null)
{{
var result = new {t}[nums.Count()];
int i = 0;
foreach (var num in nums)
result[i++] = new(_Value * num);
return result;
}}
return [];
}}
/// <summary>With Multiply</summary>
public {t}[] SetArray(IEnumerable<double?> nums)
{{
if (nums is not null)
{{
var result = new {t}[nums.Count()];
int i = 0;
foreach (var num in nums)
result[i++] = new((num ?? 0d) * _Value);
return result;
}}
return [];
}}
/// <summary>With Multiply</summary>
public {t}[] SetArray<N>(IEnumerable<N> nums) where N : INumber<N>
{{
if (nums is not null)
{{
var result = new {t}[nums.Count()];
int i = 0;
foreach (var num in nums)
result[i++] = new(num.ToDouble() * _Value);
return result;
}}
return [];
}}
/// <summary>With Multiply</summary>
public {t}[] SetArray<N>(IEnumerable<N?> nums) where N : struct, INumber<N>
{{
if (nums is not null)
{{
var result = new {t}[nums.Count()];
int i = 0;
foreach (var num in nums)
result[i++] = new(num.ToDouble() * _Value);
return result;
}}
return [];
}}");
// SetList
sb.AppendLine($@"
/// <summary>With Multiply</summary>
public List<{t}> SetList(IEnumerable<double> nums)
{{
if (nums is not null)
{{
var result = new List<{t}>(nums.Count());
foreach (var num in nums)
result.Add(new(_Value * num));
return result;
}}
return [];
}}
/// <summary>With Multiply</summary>
public List<{t}> SetList(IEnumerable<double?> nums)
{{
if (nums is not null)
{{
var result = new List<{t}>(nums.Count());
foreach (var num in nums)
result.Add(new((num ?? 0d) * _Value));
return result;
}}
return [];
}}
/// <summary>With Multiply</summary>
public List<{t}> SetList<N>(IEnumerable<N> nums) where N : INumber<N>
{{
if (nums is not null)
{{
var result = new List<{t}>(nums.Count());
foreach (var num in nums)
result.Add(new(_Value * num.ToDouble()));
return result;
}}
return [];
}}
/// <summary>With Multiply</summary>
public List<{t}> SetList<N>(IEnumerable<N?> nums) where N : struct, INumber<N>
{{
if (nums is not null)
{{
var result = new List<{t}>(nums.Count());
foreach (var num in nums)
result.Add(new(_Value * num.ToDouble()));
return result;
}}
return [];
}}");
}
private static void GenerateJsonConverter(StringBuilder sb, string t)
{
sb.AppendLine($@"
public class {t}Converter : System.Text.Json.Serialization.JsonConverter<{t}>
{{
private static readonly CultureInfo Culture = CultureInfo.InvariantCulture;
public override {t} Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{{
double doubleValue;
if (reader.TokenType == JsonTokenType.String)
{{
if (!double.TryParse(reader.GetString(), Culture, out doubleValue))
throw new JsonException($""Не удалось преобразовать строковое значение в double для метрики {t}."");
}}
else
{{
doubleValue = reader.GetDouble();
}}
return new(doubleValue);
}}
public override void Write(Utf8JsonWriter writer, {t} value, JsonSerializerOptions options)
{{
writer.WriteNumberValue(value._Value);
}}
public override void WriteAsPropertyName(Utf8JsonWriter writer, {t} value, JsonSerializerOptions options)
{{
writer.WritePropertyName(value._Value.ToString(""R"", Culture));
}}
public override {t} ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{{
string propertyName = reader.GetString()!;
if (!double.TryParse(propertyName, Culture, out double doubleValue))
throw new JsonException($""Невалидное числовое значение в ключе свойства JSON: '{{propertyName}}' для метрики {t}."");
return new(doubleValue);
}}
}}");
}
private readonly struct UnitInfo(string ns, string typeName, HashSet<string> existingMemberNames, HashSet<string> existingOperatorNames)
{
public string Namespace { get; } = ns;
public string TypeName { get; } = typeName;
public HashSet<string> ExistingMemberNames { get; } = existingMemberNames;
public HashSet<string> ExistingOperatorNames { get; } = existingOperatorNames;
}
}