diff --git a/UnitsNet.Xp.SrcGen/Directory.Build.props b/UnitsNet.Xp.SrcGen/Directory.Build.props new file mode 100644 index 0000000000..44ac0bb5cd --- /dev/null +++ b/UnitsNet.Xp.SrcGen/Directory.Build.props @@ -0,0 +1,3 @@ + + + diff --git a/UnitsNet.Xp.SrcGen/Directory.Packages.props b/UnitsNet.Xp.SrcGen/Directory.Packages.props new file mode 100644 index 0000000000..e1b79cd0b3 --- /dev/null +++ b/UnitsNet.Xp.SrcGen/Directory.Packages.props @@ -0,0 +1,6 @@ + + + + false + + \ No newline at end of file diff --git a/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGen.sln b/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGen.sln new file mode 100644 index 0000000000..75e39acb0e --- /dev/null +++ b/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGen.sln @@ -0,0 +1,34 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitsNet.Xp.SrcGenDemo", "UnitsNet.Xp.SrcGenDemo\UnitsNet.Xp.SrcGenDemo.csproj", "{B30706F0-AA00-41BD-84AA-6E2A96DCAEB5}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitsNet.Xp.SrcGen", "UnitsNet.Xp.SrcGen\UnitsNet.Xp.SrcGen.csproj", "{D635D0D1-D745-43A7-A04A-75995801753E}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "_Files", "_Files", "{8775A1CE-645B-45F2-82B2-1C94D56FA134}" + ProjectSection(SolutionItems) = preProject + Directory.Build.props = Directory.Build.props + Directory.Packages.props = Directory.Packages.props + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitsNet.Xp.SrcGenDummySource", "UnitsNet.Xp.SrcGenDummySource\UnitsNet.Xp.SrcGenDummySource.csproj", "{8DBEADB1-CDEF-4841-9AAA-C4D36C6C541A}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {B30706F0-AA00-41BD-84AA-6E2A96DCAEB5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B30706F0-AA00-41BD-84AA-6E2A96DCAEB5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B30706F0-AA00-41BD-84AA-6E2A96DCAEB5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B30706F0-AA00-41BD-84AA-6E2A96DCAEB5}.Release|Any CPU.Build.0 = Release|Any CPU + {D635D0D1-D745-43A7-A04A-75995801753E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D635D0D1-D745-43A7-A04A-75995801753E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D635D0D1-D745-43A7-A04A-75995801753E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D635D0D1-D745-43A7-A04A-75995801753E}.Release|Any CPU.Build.0 = Release|Any CPU + {8DBEADB1-CDEF-4841-9AAA-C4D36C6C541A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8DBEADB1-CDEF-4841-9AAA-C4D36C6C541A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8DBEADB1-CDEF-4841-9AAA-C4D36C6C541A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8DBEADB1-CDEF-4841-9AAA-C4D36C6C541A}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGen/IsExternalInit.cs b/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGen/IsExternalInit.cs new file mode 100644 index 0000000000..c270d04a19 --- /dev/null +++ b/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGen/IsExternalInit.cs @@ -0,0 +1,10 @@ +// using System.ComponentModel; +// +// // ReSharper disable once CheckNamespace +// namespace System.Runtime.CompilerServices; +// +// /// +// /// Workaround for netstandard2.0 and record types and init properties: https://stackoverflow.com/a/62656145/134761 +// /// +// [EditorBrowsable(EditorBrowsableState.Never)] +// internal class IsExternalInit{} diff --git a/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGen/QuantitySourceGenerator.cs b/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGen/QuantitySourceGenerator.cs new file mode 100644 index 0000000000..6c4d8c18ff --- /dev/null +++ b/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGen/QuantitySourceGenerator.cs @@ -0,0 +1,134 @@ +// 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.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace UnitsNet.Xp.SrcGen +{ + [Generator] + public class QuantitySourceGenerator : ISourceGenerator + { + private const string SrcQuantitySource = + """ + namespace UnitsNetSrcGen + { + public interface ISrcQuantity where TUnitEnum : System.Enum + { + double Value { get; } + TUnitEnum Unit { get; } + } + } + """; + + private const string LengthSource = """ + namespace UnitsNetSrcGen + { + public enum LengthUnit + { + Centimeter, + Meter, + } + + public struct Length : ISrcQuantity + { + public Length(double value, LengthUnit unit) + { + Value = value; + Unit = unit; + } + + public required double Value { get; init; } + public required LengthUnit Unit { get; init; } + } + } + """; + + private const string MassSource = """ + namespace UnitsNetSrcGen + { + public enum MassUnit + { + Gram, + Kilogram, + } + + public struct Mass : ISrcQuantity + { + public Mass(double value, MassUnit unit) + { + Value = value; + Unit = unit; + } + + public required double Value { get; init; } + public required MassUnit Unit { get; init; } + } + } + """; + + private const string AttributeSource = + """ + [System.AttributeUsage(System.AttributeTargets.Assembly, AllowMultiple = false)] + internal sealed class UnitsNetSrcGenInitAttribute : System.Attribute + { + public string[] QuantityNames { get; } + + public UnitsNetSrcGenInitAttribute(string quantityNames) + => (QuantityNames) = (quantityNames.Split(',').Select(str => str.Trim()).ToArray()); + } + """; + + public void Execute(GeneratorExecutionContext context) + { + SyntaxReceiver rx = (SyntaxReceiver)context.SyntaxContextReceiver!; + foreach (string quantityName in rx.QuantityNames) + { + string source = quantityName switch + { + "Length" => LengthSource, + "Mass" => MassSource, + _ => "", + }; + + if (source != "") + { + context.AddSource($"UnitsNetSrc_{quantityName}.g.cs", source); + } + } + } + + public void Initialize(GeneratorInitializationContext context) + { + context.RegisterForPostInitialization((pi) => + { + pi.AddSource("UnitsNetSrcGenInit.g.cs", AttributeSource); + pi.AddSource("UnitsNetSrc_ISrcQuantity.g.cs", SrcQuantitySource); + }); + context.RegisterForSyntaxNotifications(() => new SyntaxReceiver()); + } + + private class SyntaxReceiver : ISyntaxContextReceiver + { + public List QuantityNames = new(); + + public void OnVisitSyntaxNode(GeneratorSyntaxContext context) + { + // find all valid mustache attributes + if (context.Node is AttributeSyntax attrib + && context.SemanticModel.GetTypeInfo(attrib).Type?.ToDisplayString() == "UnitsNetSrcGenInitAttribute") + { + // string[] quantityNames = (string[])context.SemanticModel.GetConstantValue(attrib.ArgumentList.Arguments[0].Expression).Value; + IList quantityNames = ((string)context.SemanticModel.GetConstantValue(attrib.ArgumentList!.Arguments[0].Expression).Value).Split(',').Select(str => str.Trim()).ToList(); + + // string template = context.SemanticModel.GetConstantValue(attrib.ArgumentList.Arguments[1].Expression).ToString(); + // string hash = context.SemanticModel.GetConstantValue(attrib.ArgumentList.Arguments[2].Expression).ToString(); + + QuantityNames.AddRange(quantityNames); + } + } + } + } +} diff --git a/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGen.csproj b/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGen.csproj new file mode 100644 index 0000000000..f1f2564bba --- /dev/null +++ b/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGen.csproj @@ -0,0 +1,18 @@ + + + + netstandard2.0 + latest + + + true + latest + + + + + + + + + diff --git a/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGenDemo/Generated/net8.0/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGen.QuantitySourceGenerator/UnitsNetSrcGenInit.g.cs b/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGenDemo/Generated/net8.0/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGen.QuantitySourceGenerator/UnitsNetSrcGenInit.g.cs new file mode 100644 index 0000000000..231cd9edb0 --- /dev/null +++ b/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGenDemo/Generated/net8.0/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGen.QuantitySourceGenerator/UnitsNetSrcGenInit.g.cs @@ -0,0 +1,8 @@ +[System.AttributeUsage(System.AttributeTargets.Assembly, AllowMultiple = false)] +internal sealed class UnitsNetSrcGenInitAttribute : System.Attribute +{ + public string[] QuantityNames { get; } + + public UnitsNetSrcGenInitAttribute(string quantityNames) + => (QuantityNames) = (quantityNames.Split(',').Select(str => str.Trim()).ToArray()); +} \ No newline at end of file diff --git a/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGenDemo/Generated/net8.0/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGen.QuantitySourceGenerator/UnitsNetSrc_ISrcQuantity.g.cs b/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGenDemo/Generated/net8.0/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGen.QuantitySourceGenerator/UnitsNetSrc_ISrcQuantity.g.cs new file mode 100644 index 0000000000..3cba4bee12 --- /dev/null +++ b/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGenDemo/Generated/net8.0/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGen.QuantitySourceGenerator/UnitsNetSrc_ISrcQuantity.g.cs @@ -0,0 +1,8 @@ +namespace UnitsNetSrcGen +{ + public interface ISrcQuantity where TUnitEnum : System.Enum + { + double Value { get; } + TUnitEnum Unit { get; } + } +} \ No newline at end of file diff --git a/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGenDemo/Generated/net8.0/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGen.QuantitySourceGenerator/UnitsNetSrc_Length.g.cs b/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGenDemo/Generated/net8.0/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGen.QuantitySourceGenerator/UnitsNetSrc_Length.g.cs new file mode 100644 index 0000000000..4de5fd644b --- /dev/null +++ b/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGenDemo/Generated/net8.0/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGen.QuantitySourceGenerator/UnitsNetSrc_Length.g.cs @@ -0,0 +1,20 @@ +namespace UnitsNetSrcGen +{ + public enum LengthUnit + { + Centimeter, + Meter, + } + + public struct Length : ISrcQuantity + { + public Length(double value, LengthUnit unit) + { + Value = value; + Unit = unit; + } + + public required double Value { get; init; } + public required LengthUnit Unit { get; init; } + } + } \ No newline at end of file diff --git a/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGenDemo/Generated/net8.0/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGen.QuantitySourceGenerator/UnitsNetSrc_Mass.g.cs b/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGenDemo/Generated/net8.0/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGen.QuantitySourceGenerator/UnitsNetSrc_Mass.g.cs new file mode 100644 index 0000000000..820342ff9a --- /dev/null +++ b/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGenDemo/Generated/net8.0/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGen.QuantitySourceGenerator/UnitsNetSrc_Mass.g.cs @@ -0,0 +1,20 @@ +namespace UnitsNetSrcGen +{ + public enum MassUnit + { + Gram, + Kilogram, + } + + public struct Mass : ISrcQuantity + { + public Mass(double value, MassUnit unit) + { + Value = value; + Unit = unit; + } + + public required double Value { get; init; } + public required MassUnit Unit { get; init; } + } +} \ No newline at end of file diff --git a/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGenDemo/Program.Attributes.cs b/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGenDemo/Program.Attributes.cs new file mode 100644 index 0000000000..c39ede56ef --- /dev/null +++ b/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGenDemo/Program.Attributes.cs @@ -0,0 +1 @@ +[assembly: UnitsNetSrcGenInit("Length,Mass")] diff --git a/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGenDemo/Program.cs b/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGenDemo/Program.cs new file mode 100644 index 0000000000..1d9d8440ce --- /dev/null +++ b/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGenDemo/Program.cs @@ -0,0 +1,7 @@ +using UnitsNetSrcGen; + +var l = new Length {Value = 10, Unit = LengthUnit.Centimeter}; +var m = new Mass { Value = 20, Unit = MassUnit.Kilogram }; + +Console.WriteLine($"Length: {l.Value} {l.Unit}"); +Console.WriteLine($"Mass: {m.Value} {m.Unit}"); diff --git a/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGenDemo/UnitsNet.Xp.SrcGenDemo.csproj b/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGenDemo/UnitsNet.Xp.SrcGenDemo.csproj new file mode 100644 index 0000000000..a5ea82266a --- /dev/null +++ b/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGenDemo/UnitsNet.Xp.SrcGenDemo.csproj @@ -0,0 +1,23 @@ + + + + Exe + net8.0 + enable + enable + + + true + + Generated + + $(GeneratedFolder)\$(TargetFramework) + + + + + + + diff --git a/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGenDummySource/DummySources.cs b/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGenDummySource/DummySources.cs new file mode 100644 index 0000000000..b3efd85bd9 --- /dev/null +++ b/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGenDummySource/DummySources.cs @@ -0,0 +1,52 @@ + +/* + * Dummy sources for copy & paste into c# source generator strings in UnitsNet.Xp.SrcGen/QuantitySourceGenerator.cs. + */ +// ReSharper disable once CheckNamespace +namespace UnitsNet.Xp.SrcGen; + +/// +/// Represents a quantity. +/// +public interface ISrcQuantity where TUnitEnum : System.Enum +{ + double Value { get; } + TUnitEnum Unit { get; } +} + +public enum LengthUnit +{ + Centimeter, + Meter, +} + +public struct Length : ISrcQuantity +{ + public Length(double value, LengthUnit unit) + { + Value = value; + Unit = unit; + } + + public required double Value { get; init; } + public required LengthUnit Unit { get; init; } +} + +public enum MassUnit +{ + Gram, + Kilogram, +} + +public struct Mass : ISrcQuantity +{ + public Mass(double value, MassUnit unit) + { + Value = value; + Unit = unit; + } + + public required double Value { get; init; } + public required MassUnit Unit { get; init; } +} + diff --git a/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGenDummySource/UnitsNet.Xp.SrcGenDummySource.csproj b/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGenDummySource/UnitsNet.Xp.SrcGenDummySource.csproj new file mode 100644 index 0000000000..997e50b032 --- /dev/null +++ b/UnitsNet.Xp.SrcGen/UnitsNet.Xp.SrcGenDummySource/UnitsNet.Xp.SrcGenDummySource.csproj @@ -0,0 +1,9 @@ + + + + net8.0 + disable + enable + + +