Skip to content

Commit ea94306

Browse files
committed
Merge remote-tracking branch 'origin/release/v6' into remove-decimal-support
2 parents c37bae4 + aeb0cd7 commit ea94306

File tree

317 files changed

+3855
-2867
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

317 files changed

+3855
-2867
lines changed

CodeGen/Generators/NanoFrameworkGen/QuantityGenerator.cs

+5-5
Original file line numberDiff line numberDiff line change
@@ -73,17 +73,17 @@ public struct {_quantity.Name}
7373
}}
7474
7575
/// <summary>
76-
/// The base unit of Duration, which is Second. All conversions go via this value.
76+
/// The base unit of {_quantity.Name}, which is Second. All conversions go via this value.
7777
/// </summary>
7878
public static {_unitEnumName} BaseUnit {{ get; }} = {_unitEnumName}.{_quantity.BaseUnit};
7979
8080
/// <summary>
81-
/// Represents the largest possible value of Duration
81+
/// Represents the largest possible value of {_quantity.Name}.
8282
/// </summary>
8383
public static {_quantity.Name} MaxValue {{ get; }} = new {_quantity.Name}(double.MaxValue, BaseUnit);
8484
8585
/// <summary>
86-
/// Represents the smallest possible value of Duration
86+
/// Represents the smallest possible value of {_quantity.Name}.
8787
/// </summary>
8888
public static {_quantity.Name} MinValue {{ get; }} = new {_quantity.Name}(double.MinValue, BaseUnit);
8989
@@ -178,9 +178,9 @@ private void GenerateConversionMethods()
178178
public double As({_unitEnumName} unit) => GetValueAs(unit);
179179
180180
/// <summary>
181-
/// Converts this Duration to another Duration with the unit representation <paramref name=""unit"" />.
181+
/// Converts this {_quantity.Name} to another {_quantity.Name} with the unit representation <paramref name=""unit"" />.
182182
/// </summary>
183-
/// <returns>A Duration with the specified unit.</returns>
183+
/// <returns>A {_quantity.Name} with the specified unit.</returns>
184184
public {_quantity.Name} ToUnit({_unitEnumName} unit)
185185
{{
186186
var convertedValue = GetValueAs(unit);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
// Licensed under MIT No Attribution, see LICENSE file at the root.
2+
// Copyright 2013 Andreas Gullberg Larsen ([email protected]). Maintained at https://github.com/angularsen/UnitsNet.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.IO;
7+
using System.Linq;
8+
using CodeGen.Exceptions;
9+
using CodeGen.JsonTypes;
10+
using Newtonsoft.Json;
11+
12+
namespace CodeGen.Generators
13+
{
14+
/// <summary>
15+
/// Parses the JSON file that defines the relationships (operators) between quantities
16+
/// and applies them to the parsed quantity objects.
17+
/// </summary>
18+
internal static class QuantityRelationsParser
19+
{
20+
/// <summary>
21+
/// Parse and apply relations to quantities.
22+
///
23+
/// The relations are defined in UnitRelations.json
24+
/// Each defined relation can be applied multiple times to one or two quantities depending on the operator and the operands.
25+
///
26+
/// The format of a relation definition is "Quantity.Unit operator Quantity.Unit = Quantity.Unit" (See examples below).
27+
/// "double" can be used as a unitless operand.
28+
/// "1" can be used as the left operand to define inverse relations.
29+
/// </summary>
30+
/// <example>
31+
/// [
32+
/// "Power.Watt = ElectricPotential.Volt * ElectricCurrent.Ampere",
33+
/// "Speed.MeterPerSecond = Length.Meter / Duration.Second",
34+
/// "ReciprocalLength.InverseMeter = 1 / Length.Meter"
35+
/// ]
36+
/// </example>
37+
/// <param name="rootDir">Repository root directory.</param>
38+
/// <param name="quantities">List of previously parsed Quantity objects.</param>
39+
public static void ParseAndApplyRelations(string rootDir, Quantity[] quantities)
40+
{
41+
var quantityDictionary = quantities.ToDictionary(q => q.Name, q => q);
42+
43+
// Add double and 1 as pseudo-quantities to validate relations that use them.
44+
var pseudoQuantity = new Quantity { Name = null!, Units = [new Unit { SingularName = null! }] };
45+
quantityDictionary["double"] = pseudoQuantity with { Name = "double" };
46+
quantityDictionary["1"] = pseudoQuantity with { Name = "1" };
47+
48+
var relations = ParseRelations(rootDir, quantityDictionary);
49+
50+
// Because multiplication is commutative, we can infer the other operand order.
51+
relations.AddRange(relations
52+
.Where(r => r.Operator is "*" or "inverse" && r.LeftQuantity != r.RightQuantity)
53+
.Select(r => r with
54+
{
55+
LeftQuantity = r.RightQuantity,
56+
LeftUnit = r.RightUnit,
57+
RightQuantity = r.LeftQuantity,
58+
RightUnit = r.LeftUnit,
59+
})
60+
.ToList());
61+
62+
// We can infer TimeSpan relations from Duration relations.
63+
var timeSpanQuantity = pseudoQuantity with { Name = "TimeSpan" };
64+
relations.AddRange(relations
65+
.Where(r => r.LeftQuantity.Name is "Duration")
66+
.Select(r => r with { LeftQuantity = timeSpanQuantity })
67+
.ToList());
68+
relations.AddRange(relations
69+
.Where(r => r.RightQuantity.Name is "Duration")
70+
.Select(r => r with { RightQuantity = timeSpanQuantity })
71+
.ToList());
72+
73+
// Sort all relations to keep generated operators in a consistent order.
74+
relations.Sort();
75+
76+
var duplicates = relations
77+
.GroupBy(r => r.SortString)
78+
.Where(g => g.Count() > 1)
79+
.Select(g => g.Key)
80+
.ToList();
81+
82+
if (duplicates.Any())
83+
{
84+
var list = string.Join("\n ", duplicates);
85+
throw new UnitsNetCodeGenException($"Duplicate inferred relations:\n {list}");
86+
}
87+
88+
foreach (var quantity in quantities)
89+
{
90+
var quantityRelations = new List<QuantityRelation>();
91+
92+
foreach (var relation in relations)
93+
{
94+
if (relation.LeftQuantity == quantity)
95+
{
96+
// The left operand of a relation is responsible for generating the operator.
97+
quantityRelations.Add(relation);
98+
}
99+
else if (relation.RightQuantity == quantity && relation.LeftQuantity.Name is "double" or "TimeSpan")
100+
{
101+
// Because we cannot add generated operators to double or TimeSpan, we make the right operand responsible in this case.
102+
quantityRelations.Add(relation);
103+
}
104+
}
105+
106+
quantity.Relations = quantityRelations.ToArray();
107+
}
108+
}
109+
110+
private static List<QuantityRelation> ParseRelations(string rootDir, IReadOnlyDictionary<string, Quantity> quantities)
111+
{
112+
var relationsFileName = Path.Combine(rootDir, "Common/UnitRelations.json");
113+
114+
try
115+
{
116+
var text = File.ReadAllText(relationsFileName);
117+
var relationStrings = JsonConvert.DeserializeObject<SortedSet<string>>(text) ?? [];
118+
119+
var parsedRelations = relationStrings.Select(relationString => ParseRelation(relationString, quantities)).ToList();
120+
121+
// File parsed successfully, save it back to disk in the sorted state.
122+
File.WriteAllText(relationsFileName, JsonConvert.SerializeObject(relationStrings, Formatting.Indented));
123+
124+
return parsedRelations;
125+
}
126+
catch (Exception e)
127+
{
128+
throw new UnitsNetCodeGenException($"Error parsing relations file: {relationsFileName}", e);
129+
}
130+
}
131+
132+
private static QuantityRelation ParseRelation(string relationString, IReadOnlyDictionary<string, Quantity> quantities)
133+
{
134+
var segments = relationString.Split(' ');
135+
136+
if (segments is not [_, "=", _, "*" or "/", _])
137+
{
138+
throw new Exception($"Invalid relation string: {relationString}");
139+
}
140+
141+
var @operator = segments[3];
142+
var left = segments[2].Split('.');
143+
var right = segments[4].Split('.');
144+
var result = segments[0].Split('.');
145+
146+
var leftQuantity = GetQuantity(left[0]);
147+
var rightQuantity = GetQuantity(right[0]);
148+
var resultQuantity = GetQuantity(result[0]);
149+
150+
var leftUnit = GetUnit(leftQuantity, left.ElementAtOrDefault(1));
151+
var rightUnit = GetUnit(rightQuantity, right.ElementAtOrDefault(1));
152+
var resultUnit = GetUnit(resultQuantity, result.ElementAtOrDefault(1));
153+
154+
if (leftQuantity.Name == "1")
155+
{
156+
@operator = "inverse";
157+
leftQuantity = resultQuantity;
158+
leftUnit = resultUnit;
159+
}
160+
161+
return new QuantityRelation
162+
{
163+
Operator = @operator,
164+
LeftQuantity = leftQuantity,
165+
LeftUnit = leftUnit,
166+
RightQuantity = rightQuantity,
167+
RightUnit = rightUnit,
168+
ResultQuantity = resultQuantity,
169+
ResultUnit = resultUnit
170+
};
171+
172+
Quantity GetQuantity(string quantityName)
173+
{
174+
if (!quantities.TryGetValue(quantityName, out var quantity))
175+
{
176+
throw new Exception($"Undefined quantity {quantityName} in relation string: {relationString}");
177+
}
178+
179+
return quantity;
180+
}
181+
182+
Unit GetUnit(Quantity quantity, string? unitName)
183+
{
184+
try
185+
{
186+
return quantity.Units.First(u => u.SingularName == unitName);
187+
}
188+
catch (InvalidOperationException)
189+
{
190+
throw new Exception($"Undefined unit {unitName} in relation string: {relationString}");
191+
}
192+
}
193+
}
194+
}
195+
}

CodeGen/Generators/UnitsNetGen/QuantityGenerator.cs

+121-4
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,12 @@ public string Generate()
3737
using System;
3838
using System.Diagnostics.CodeAnalysis;
3939
using System.Globalization;
40-
using System.Linq;
41-
using System.Runtime.Serialization;
40+
using System.Linq;");
41+
if (_quantity.Relations.Any(r => r.Operator is "*" or "/"))
42+
Writer.WL(@"#if NET7_0_OR_GREATER
43+
using System.Numerics;
44+
#endif");
45+
Writer.WL(@"using System.Runtime.Serialization;
4246
using UnitsNet.InternalHelpers;
4347
using UnitsNet.Units;
4448
@@ -65,6 +69,35 @@ namespace UnitsNet
6569
public readonly partial struct {_quantity.Name} :
6670
{(_quantity.GenerateArithmetic ? "IArithmeticQuantity" : "IQuantity")}<{_quantity.Name}, {_unitEnumName}>,");
6771

72+
if (_quantity.Relations.Any(r => r.Operator is "*" or "/"))
73+
{
74+
Writer.WL(@$"
75+
#if NET7_0_OR_GREATER");
76+
foreach (var relation in _quantity.Relations)
77+
{
78+
if (relation.LeftQuantity == _quantity)
79+
{
80+
switch (relation.Operator)
81+
{
82+
case "*":
83+
Writer.W(@"
84+
IMultiplyOperators");
85+
break;
86+
case "/":
87+
Writer.W(@"
88+
IDivisionOperators");
89+
break;
90+
default:
91+
continue;
92+
}
93+
Writer.WL($"<{relation.LeftQuantity.Name}, {relation.RightQuantity.Name}, {relation.ResultQuantity.Name}>,");
94+
}
95+
}
96+
97+
Writer.WL(@$"
98+
#endif");
99+
}
100+
68101
Writer.WL(@$"
69102
IComparable,
70103
IComparable<{_quantity.Name}>,
@@ -77,13 +110,13 @@ namespace UnitsNet
77110
/// <summary>
78111
/// The numeric value this quantity was constructed with.
79112
/// </summary>
80-
[DataMember(Name = ""Value"", Order = 0)]
113+
[DataMember(Name = ""Value"", Order = 1)]
81114
private readonly double _value;
82115
83116
/// <summary>
84117
/// The unit this quantity was constructed with.
85118
/// </summary>
86-
[DataMember(Name = ""Unit"", Order = 1)]
119+
[DataMember(Name = ""Unit"", Order = 2)]
87120
private readonly {_unitEnumName}? _unit;
88121
");
89122
GenerateStaticConstructor();
@@ -95,6 +128,7 @@ namespace UnitsNet
95128
GenerateStaticFactoryMethods();
96129
GenerateStaticParseMethods();
97130
GenerateArithmeticOperators();
131+
GenerateRelationalOperators();
98132
GenerateEqualityAndComparison();
99133
GenerateConversionMethods();
100134
GenerateToString();
@@ -684,6 +718,89 @@ private void GenerateLogarithmicArithmeticOperators()
684718
" );
685719
}
686720

721+
/// <summary>
722+
/// Generates operators that express relations between quantities as applied by <see cref="QuantityRelationsParser" />.
723+
/// </summary>
724+
private void GenerateRelationalOperators()
725+
{
726+
if (!_quantity.Relations.Any()) return;
727+
728+
Writer.WL($@"
729+
#region Relational Operators
730+
");
731+
732+
foreach (QuantityRelation relation in _quantity.Relations)
733+
{
734+
if (relation.Operator == "inverse")
735+
{
736+
Writer.WL($@"
737+
/// <summary>Calculates the inverse of this quantity.</summary>
738+
/// <returns>The corresponding inverse quantity, <see cref=""{relation.RightQuantity.Name}""/>.</returns>
739+
public {relation.RightQuantity.Name} Inverse()
740+
{{
741+
return {relation.LeftUnit.PluralName} == 0.0 ? {relation.RightQuantity.Name}.Zero : {relation.RightQuantity.Name}.From{relation.RightUnit.PluralName}(1 / {relation.LeftUnit.PluralName});
742+
}}
743+
");
744+
}
745+
else
746+
{
747+
var leftParameter = relation.LeftQuantity.Name.ToCamelCase();
748+
var leftConversionProperty = relation.LeftUnit.PluralName;
749+
var rightParameter = relation.RightQuantity.Name.ToCamelCase();
750+
var rightConversionProperty = relation.RightUnit.PluralName;
751+
752+
if (relation.LeftQuantity.Name is nameof(TimeSpan))
753+
{
754+
leftConversionProperty = "Total" + leftConversionProperty;
755+
}
756+
757+
if (relation.RightQuantity.Name is nameof(TimeSpan))
758+
{
759+
rightConversionProperty = "Total" + rightConversionProperty;
760+
}
761+
762+
if (leftParameter == rightParameter)
763+
{
764+
leftParameter = "left";
765+
rightParameter = "right";
766+
}
767+
768+
var leftPart = $"{leftParameter}.{leftConversionProperty}";
769+
var rightPart = $"{rightParameter}.{rightConversionProperty}";
770+
771+
if (leftParameter is "double")
772+
{
773+
leftParameter = leftPart = "value";
774+
}
775+
776+
if (rightParameter is "double")
777+
{
778+
rightParameter = rightPart = "value";
779+
}
780+
781+
var expression = $"{leftPart} {relation.Operator} {rightPart}";
782+
783+
if (relation.ResultQuantity.Name is not ("double" or "decimal"))
784+
{
785+
expression = $"{relation.ResultQuantity.Name}.From{relation.ResultUnit.PluralName}({expression})";
786+
}
787+
788+
Writer.WL($@"
789+
/// <summary>Get <see cref=""{relation.ResultQuantity.Name}""/> from <see cref=""{relation.LeftQuantity.Name}""/> {relation.Operator} <see cref=""{relation.RightQuantity.Name}""/>.</summary>
790+
public static {relation.ResultQuantity.Name} operator {relation.Operator}({relation.LeftQuantity.Name} {leftParameter}, {relation.RightQuantity.Name} {rightParameter})
791+
{{
792+
return {expression};
793+
}}
794+
");
795+
}
796+
}
797+
798+
Writer.WL($@"
799+
800+
#endregion
801+
");
802+
}
803+
687804
private void GenerateEqualityAndComparison()
688805
{
689806
Writer.WL($@"

0 commit comments

Comments
 (0)