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 QWERTYkez.Mensura { [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( typeSymbol.GetMembers() .Where(m => m.Kind == SymbolKind.Method || m.Kind == SymbolKind.Property || m.Kind == SymbolKind.Field) .Select(m => m.Name)); var existingOperatorNames = new HashSet( 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 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("// "); 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($@" /// With Multiply public {t}[] SetArray(IEnumerable 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 []; }} /// With Multiply public {t}[] SetArray(IEnumerable 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 []; }} /// With Multiply public {t}[] SetArray(IEnumerable nums) where N : INumber {{ 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 []; }} /// With Multiply public {t}[] SetArray(IEnumerable nums) where N : struct, INumber {{ 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($@" /// With Multiply public List<{t}> SetList(IEnumerable 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 []; }} /// With Multiply public List<{t}> SetList(IEnumerable 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 []; }} /// With Multiply public List<{t}> SetList(IEnumerable nums) where N : INumber {{ 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 []; }} /// With Multiply public List<{t}> SetList(IEnumerable nums) where N : struct, INumber {{ 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 existingMemberNames, HashSet existingOperatorNames) { public string Namespace { get; } = ns; public string TypeName { get; } = typeName; public HashSet ExistingMemberNames { get; } = existingMemberNames; public HashSet ExistingOperatorNames { get; } = existingOperatorNames; } }