Добавьте файлы проекта.
This commit is contained in:
226
QWERTYkez.Mensura.Generator/OperatorsGenerator.cs
Normal file
226
QWERTYkez.Mensura.Generator/OperatorsGenerator.cs
Normal file
@@ -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<KeyValuePair<ClassData, ImmutableArray<Operation>>>(
|
||||||
|
(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<OperatorDeclarationSyntax>()
|
||||||
|
.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<OperatorDeclarationSyntax>()
|
||||||
|
.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<KeyValuePair<ClassData, ImmutableArray<Operation>>> 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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
|
<LangVersion>latest</LangVersion>
|
||||||
|
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||||
|
<NoWarn>1701;1702;IDE0079;MVVMTK0034</NoWarn>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||||
|
<NoWarn>1701;1702;IDE0079;MVVMTK0034</NoWarn>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
|
||||||
|
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="4.14.0">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="5.0.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
397
QWERTYkez.Mensura.Generator/UnitOperatorsGenerator.cs
Normal file
397
QWERTYkez.Mensura.Generator/UnitOperatorsGenerator.cs
Normal file
@@ -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<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;
|
||||||
|
}
|
||||||
|
}
|
||||||
4
QWERTYkez.Mensura.slnx
Normal file
4
QWERTYkez.Mensura.slnx
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<Solution>
|
||||||
|
<Project Path="QWERTYkez.Mensura.Generator/QWERTYkez.Mensura.Generator.csproj" />
|
||||||
|
<Project Path="QWERTYkez.Mensura/QWERTYkez.Mensura.csproj" />
|
||||||
|
</Solution>
|
||||||
9
QWERTYkez.Mensura/QWERTYkez.Mensura.csproj
Normal file
9
QWERTYkez.Mensura/QWERTYkez.Mensura.csproj
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
Reference in New Issue
Block a user