2026-05-28 10:45:35 +07:00
|
|
|
|
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",
|
2026-05-28 16:44:39 +07:00
|
|
|
|
SourceText.From(@"namespace QWERTYkez.Mensura
|
2026-05-28 10:45:35 +07:00
|
|
|
|
{
|
|
|
|
|
|
[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;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|