diff --git a/QWERTYkez.Mensura.Generator/OperatorsGenerator.cs b/QWERTYkez.Mensura.Generator/OperatorsGenerator.cs new file mode 100644 index 0000000..55c1725 --- /dev/null +++ b/QWERTYkez.Mensura.Generator/OperatorsGenerator.cs @@ -0,0 +1,226 @@ +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"; + + private const string AttributeSource = @"namespace MetricSystem.Generator; + +[System.AttributeUsage(System.AttributeTargets.Class | System.AttributeTargets.Method, AllowMultiple = false, Inherited = false)] +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>>( + (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() + .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() + .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>> 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; +} \ No newline at end of file diff --git a/QWERTYkez.Mensura.Generator/QWERTYkez.Mensura.Generator.csproj b/QWERTYkez.Mensura.Generator/QWERTYkez.Mensura.Generator.csproj new file mode 100644 index 0000000..1b8fd95 --- /dev/null +++ b/QWERTYkez.Mensura.Generator/QWERTYkez.Mensura.Generator.csproj @@ -0,0 +1,26 @@ + + + + netstandard2.0 + latest + true + + + + 1701;1702;IDE0079;MVVMTK0034 + + + + 1701;1702;IDE0079;MVVMTK0034 + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + diff --git a/QWERTYkez.Mensura.Generator/UnitOperatorsGenerator.cs b/QWERTYkez.Mensura.Generator/UnitOperatorsGenerator.cs new file mode 100644 index 0000000..62d2a53 --- /dev/null +++ b/QWERTYkez.Mensura.Generator/UnitOperatorsGenerator.cs @@ -0,0 +1,397 @@ +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( + 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; + } +} \ No newline at end of file diff --git a/QWERTYkez.Mensura.slnx b/QWERTYkez.Mensura.slnx new file mode 100644 index 0000000..2599336 --- /dev/null +++ b/QWERTYkez.Mensura.slnx @@ -0,0 +1,4 @@ + + + + diff --git a/QWERTYkez.Mensura/QWERTYkez.Mensura.csproj b/QWERTYkez.Mensura/QWERTYkez.Mensura.csproj new file mode 100644 index 0000000..ff82a64 --- /dev/null +++ b/QWERTYkez.Mensura/QWERTYkez.Mensura.csproj @@ -0,0 +1,9 @@ + + + + net6.0 + enable + enable + + + \ No newline at end of file