diff --git a/CodeGen/Generators/QuantityRelationsParser.cs b/CodeGen/Generators/QuantityRelationsParser.cs new file mode 100644 index 0000000000..9080e4e90c --- /dev/null +++ b/CodeGen/Generators/QuantityRelationsParser.cs @@ -0,0 +1,84 @@ +// Licensed under MIT No Attribution, see LICENSE file at the root. +// Copyright 2013 Andreas Gullberg Larsen (andreas.larsen84@gmail.com). Maintained at https://github.com/angularsen/UnitsNet. + +using System.Collections.Generic; +using System.IO; +using CodeGen.JsonTypes; +using Newtonsoft.Json; + +namespace CodeGen.Generators +{ + internal static class QuantityRelationsParser + { + private static List ParseRelations(string rootDir) + { + var relationsFileName = Path.Combine(rootDir, "Common/UnitRelations.json"); + return JsonConvert.DeserializeObject>(File.ReadAllText(relationsFileName)) ?? new List(); + } + + public static void ApplyRelations(string rootDir, IEnumerable quantities) + { + var relations = ParseRelations(rootDir); + var timespanRelations = new List(); + + foreach (var relation in relations) + { + relation.ResultQuantity = relation.Result?.Split('.')[0] ?? string.Empty; + relation.LeftQuantity = relation.Left?.Split('.')[0] ?? string.Empty; + relation.RightQuantity = relation.Right?.Split('.')[0] ?? string.Empty; + + if (relation.LeftQuantity == "Duration") + { + timespanRelations.Add(relation with + { + Left = relation.Left?.Replace("Duration.", "TimeSpan.Total") ?? string.Empty, + LeftQuantity = "TimeSpan", + }); + } + + if (relation.RightQuantity == "Duration") + { + timespanRelations.Add(relation with + { + Right = relation.Right?.Replace("Duration.", "TimeSpan.Total") ?? string.Empty, + RightQuantity = "TimeSpan", + }); + } + } + + relations.AddRange(timespanRelations); + + foreach (var quantity in quantities) + { + var quantityRelations = new List(); + + foreach (var relation in relations) + { + if (relation.LeftQuantity == quantity.Name) + { + quantityRelations.Add(relation); + if (relation is { Operator: "*", RightQuantity: "TimeSpan" or "double" }) + { + quantityRelations.Add(relation.Swapped()); + } + } + + if (relation.RightQuantity == quantity.Name) + { + if (relation.Operator == "inverse" || relation.Operator == "*" && relation.Left != relation.Right) + { + quantityRelations.Add(relation.Swapped()); + } + + if (relation is { Operator: "/", Left: "double" }) + { + quantityRelations.Add(relation); + } + } + } + + quantity.Relations = quantityRelations.ToArray(); + } + } + } +} diff --git a/CodeGen/Generators/UnitsNetGen/QuantityGenerator.cs b/CodeGen/Generators/UnitsNetGen/QuantityGenerator.cs index 031b5d0c55..b75cff08e8 100644 --- a/CodeGen/Generators/UnitsNetGen/QuantityGenerator.cs +++ b/CodeGen/Generators/UnitsNetGen/QuantityGenerator.cs @@ -17,6 +17,8 @@ internal class QuantityGenerator : GeneratorBase private readonly string _valueType; private readonly Unit _baseUnit; + private readonly string[] _decimalTypes = { "BitRate", "Information", "Power" }; + public QuantityGenerator(Quantity quantity) { _quantity = quantity ?? throw new ArgumentNullException(nameof(quantity)); @@ -39,8 +41,12 @@ public string Generate() using System; using System.Diagnostics.CodeAnalysis; using System.Globalization; -using System.Linq; -using System.Runtime.Serialization; +using System.Linq;"); + if (_quantity.Relations.Any(r => r.Operator is "*" or "/")) + Writer.WL(@"#if NET7_0_OR_GREATER +using System.Numerics; +#endif"); + Writer.WL(@"using System.Runtime.Serialization; using UnitsNet.InternalHelpers; using UnitsNet.Units; @@ -67,6 +73,35 @@ namespace UnitsNet public readonly partial struct {_quantity.Name} : {(_quantity.GenerateArithmetic ? "IArithmeticQuantity" : "IQuantity")}<{_quantity.Name}, {_unitEnumName}, {_quantity.ValueType}>,"); + if (_quantity.Relations.Any(r => r.Operator is "*" or "/")) + { + Writer.WL(@$" +#if NET7_0_OR_GREATER"); + foreach (var relation in _quantity.Relations) + { + if (relation.LeftQuantity == _quantity.Name) + { + switch (relation.Operator) + { + case "*": + Writer.W(@" + IMultiplyOperators"); + break; + case "/": + Writer.W(@" + IDivisionOperators"); + break; + default: + continue; + } + Writer.WL($"<{relation.LeftQuantity}, {relation.RightQuantity}, {relation.ResultQuantity}>,"); + } + } + + Writer.WL(@$" +#endif"); + } + if (_quantity.ValueType == "decimal") Writer.WL(@$" IDecimalQuantity,"); @@ -100,6 +135,7 @@ namespace UnitsNet GenerateStaticFactoryMethods(); GenerateStaticParseMethods(); GenerateArithmeticOperators(); + GenerateRelationalOperators(); GenerateEqualityAndComparison(); GenerateConversionMethods(); GenerateToString(); @@ -696,6 +732,81 @@ private void GenerateLogarithmicArithmeticOperators() " ); } + private void GenerateRelationalOperators() + { + if (!_quantity.Relations.Any()) return; + + Writer.WL($@" + #region Relational Operators +"); + + foreach (Relation relation in _quantity.Relations) + { + if (relation.Operator == "inverse") + { + Writer.WL($@" + /// Calculates the inverse of this quantity. + /// The corresponding inverse quantity, . + public {relation.RightQuantity} Inverse() + {{ + return {relation.Left.Split('.')[1]} == 0.0 ? {relation.RightQuantity}.Zero : {relation.RightQuantity}.From{relation.Right.Split('.')[1]}(1 / {relation.Left.Split('.')[1]}); + }} +"); + } + else + { + var leftParameter = relation.LeftQuantity.ToCamelCase(); + var leftConversionProperty = relation.Left.Split('.').ElementAtOrDefault(1); + var rightParameter = relation.RightQuantity.ToCamelCase(); + var rightConversionProperty = relation.Right.Split('.').ElementAtOrDefault(1); + + if (leftParameter == rightParameter) + { + leftParameter = "left"; + rightParameter = "right"; + } + + var leftPart = $"{leftParameter}.{leftConversionProperty}"; + var rightPart = $"{rightParameter}.{rightConversionProperty}"; + + if (leftParameter == "double") + { + leftParameter = "value"; + leftPart = "value"; + } + + if (rightParameter == "double") + { + rightParameter = "value"; + rightPart = "value"; + } + + var leftCast = _decimalTypes.Contains(relation.LeftQuantity) ? "(double)" : ""; + var rightCast = _decimalTypes.Contains(relation.RightQuantity) ? "(double)" : ""; + + var expression = $"{leftCast}{leftPart} {relation.Operator} {rightCast}{rightPart}"; + + if (relation.Result is not ("double" or "decimal")) + { + expression = $"{relation.Result}({expression})"; + } + + Writer.WL($@" + /// Get from {relation.Operator} . + public static {relation.ResultQuantity} operator {relation.Operator}({relation.LeftQuantity} {leftParameter}, {relation.RightQuantity} {rightParameter}) + {{ + return {expression}; + }} +"); + } + } + + Writer.WL($@" + + #endregion +"); + } + private void GenerateEqualityAndComparison() { Writer.WL($@" diff --git a/CodeGen/JsonTypes/Quantity.cs b/CodeGen/JsonTypes/Quantity.cs index 4af26113fc..633e8607cc 100644 --- a/CodeGen/JsonTypes/Quantity.cs +++ b/CodeGen/JsonTypes/Quantity.cs @@ -18,6 +18,7 @@ internal class Quantity public int LogarithmicScalingFactor = 1; public string Name = null!; public Unit[] Units = Array.Empty(); + public Relation[] Relations = Array.Empty(); public string? XmlDocRemarks; public string XmlDocSummary = null!; public string? ObsoleteText; diff --git a/CodeGen/JsonTypes/Relation.cs b/CodeGen/JsonTypes/Relation.cs new file mode 100644 index 0000000000..fc2758e9ed --- /dev/null +++ b/CodeGen/JsonTypes/Relation.cs @@ -0,0 +1,31 @@ +// Licensed under MIT No Attribution, see LICENSE file at the root. +// Copyright 2013 Andreas Gullberg Larsen (andreas.larsen84@gmail.com). Maintained at https://github.com/angularsen/UnitsNet. + +namespace CodeGen.JsonTypes +{ + internal record Relation + { + // 0649 Field is never assigned to +#pragma warning disable 0649 + + public string Operator = null!; + public string Result = null!; + public string Left = null!; + public string Right = null!; + + public string ResultQuantity = null!; + public string LeftQuantity = null!; + public string RightQuantity = null!; + + public Relation Swapped() => this with + { + Left = Right, + Right = Left, + LeftQuantity = RightQuantity, + RightQuantity = LeftQuantity + }; + + // 0649 Field is never assigned to +#pragma warning restore 0649 + } +} diff --git a/CodeGen/Program.cs b/CodeGen/Program.cs index d5589d5d1d..e929d53fd9 100644 --- a/CodeGen/Program.cs +++ b/CodeGen/Program.cs @@ -69,6 +69,8 @@ public static int Main(bool verbose = false, DirectoryInfo? repositoryRoot = nul QuantityNameToUnitEnumValues quantityNameToUnitEnumValues = UnitEnumValueAllocator.AllocateNewUnitEnumValues($"{rootDir}/Common/UnitEnumValues.g.json", quantities); + QuantityRelationsParser.ApplyRelations(rootDir, quantities); + UnitsNetGenerator.Generate(rootDir, quantities, quantityNameToUnitEnumValues); if (updateNanoFrameworkDependencies)