From 9791fd23bb201c3a246834cb0db9682d5a6a5c53 Mon Sep 17 00:00:00 2001 From: Oliver Booth Date: Thu, 6 Apr 2023 14:40:27 +0100 Subject: [PATCH 1/9] feat: add CountDigit for integer types --- CHANGELOG.md | 1 + X10D.Tests/src/Math/BigIntegerTests.cs | 55 ++++++++++++++++++++++++++ X10D.Tests/src/Math/ByteTests.cs | 44 +++++++++++++++++++++ X10D.Tests/src/Math/Int16Tests.cs | 55 ++++++++++++++++++++++++++ X10D.Tests/src/Math/Int32Tests.cs | 55 ++++++++++++++++++++++++++ X10D.Tests/src/Math/Int64Tests.cs | 55 ++++++++++++++++++++++++++ X10D.Tests/src/Math/SByteTests.cs | 55 ++++++++++++++++++++++++++ X10D.Tests/src/Math/UInt16Tests.cs | 44 +++++++++++++++++++++ X10D.Tests/src/Math/UInt32Tests.cs | 43 ++++++++++++++++++++ X10D.Tests/src/Math/UInt64Tests.cs | 44 +++++++++++++++++++++ X10D/src/Math/BigIntegerExtensions.cs | 15 +++++++ X10D/src/Math/ByteExtensions.cs | 15 +++++++ X10D/src/Math/Int16Extensions.cs | 15 +++++++ X10D/src/Math/Int32Extensions.cs | 15 +++++++ X10D/src/Math/Int64Extensions.cs | 15 +++++++ X10D/src/Math/SByteExtensions.cs | 15 +++++++ X10D/src/Math/UInt16Extensions.cs | 15 +++++++ X10D/src/Math/UInt32Extensions.cs | 15 +++++++ X10D/src/Math/UInt64Extensions.cs | 15 +++++++ 19 files changed, 586 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a0fcace..e7daff2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - X10D: Added extension methods for `DateOnly`, for parity with `DateTime` and `DateTimeOffset`. - X10D: Added math-related extension methods for `BigInteger`. - X10D: Added `Span.Replace(T, T)`. +- X10D: Added `CountDigits` for integer types. ### Changed - X10D: `DateTime.Age(DateTime)` and `DateTimeOffset.Age(DateTimeOffset)` parameter renamed from `asOf` to `referenceDate`. diff --git a/X10D.Tests/src/Math/BigIntegerTests.cs b/X10D.Tests/src/Math/BigIntegerTests.cs index bbe3b09..2151798 100644 --- a/X10D.Tests/src/Math/BigIntegerTests.cs +++ b/X10D.Tests/src/Math/BigIntegerTests.cs @@ -7,6 +7,61 @@ namespace X10D.Tests.Math; [TestFixture] public partial class BigIntegerTests { + [Test] + public void CountDigits_ShouldReturn1_Given0() + { + BigInteger value = 0; + const int expected = 1; + + int result = value.CountDigits(); + + Assert.That(result, Is.EqualTo(expected)); + } + + [Test] + public void CountDigits_ShouldReturn1_Given1() + { + BigInteger value = 1; + const int expected = 1; + + int result = value.CountDigits(); + + Assert.That(result, Is.EqualTo(expected)); + } + + [Test] + public void CountDigits_ShouldReturn1_GivenNegative1() + { + BigInteger value = -1; + const int expected = 1; + + int result = value.CountDigits(); + + Assert.That(result, Is.EqualTo(expected)); + } + + [Test] + public void CountDigits_ShouldReturn2_Given10() + { + BigInteger value = 10; + const int expected = 2; + + int result = value.CountDigits(); + + Assert.That(result, Is.EqualTo(expected)); + } + + [Test] + public void CountDigits_ShouldReturn3_Given199() + { + BigInteger value = 199; + const int expected = 3; + + int result = value.CountDigits(); + + Assert.That(result, Is.EqualTo(expected)); + } + [Test] public void DigitalRootShouldBeCorrect() { diff --git a/X10D.Tests/src/Math/ByteTests.cs b/X10D.Tests/src/Math/ByteTests.cs index e139eb1..3419f3e 100644 --- a/X10D.Tests/src/Math/ByteTests.cs +++ b/X10D.Tests/src/Math/ByteTests.cs @@ -6,6 +6,50 @@ namespace X10D.Tests.Math; [TestFixture] public partial class ByteTests { + [Test] + public void CountDigits_ShouldReturn1_Given0() + { + const byte value = 0; + const int expected = 1; + + int result = value.CountDigits(); + + Assert.That(result, Is.EqualTo(expected)); + } + + [Test] + public void CountDigits_ShouldReturn1_Given1() + { + const byte value = 1; + const int expected = 1; + + int result = value.CountDigits(); + + Assert.That(result, Is.EqualTo(expected)); + } + + [Test] + public void CountDigits_ShouldReturn2_Given10() + { + const byte value = 10; + const int expected = 2; + + int result = value.CountDigits(); + + Assert.That(result, Is.EqualTo(expected)); + } + + [Test] + public void CountDigits_ShouldReturn3_Given199() + { + const byte value = 199; + const int expected = 3; + + int result = value.CountDigits(); + + Assert.That(result, Is.EqualTo(expected)); + } + [Test] public void DigitalRootShouldBeCorrect() { diff --git a/X10D.Tests/src/Math/Int16Tests.cs b/X10D.Tests/src/Math/Int16Tests.cs index 50591cc..3823264 100644 --- a/X10D.Tests/src/Math/Int16Tests.cs +++ b/X10D.Tests/src/Math/Int16Tests.cs @@ -6,6 +6,61 @@ namespace X10D.Tests.Math; [TestFixture] public partial class Int16Tests { + [Test] + public void CountDigits_ShouldReturn1_Given0() + { + const short value = 0; + const int expected = 1; + + int result = value.CountDigits(); + + Assert.That(result, Is.EqualTo(expected)); + } + + [Test] + public void CountDigits_ShouldReturn1_Given1() + { + const short value = 1; + const int expected = 1; + + int result = value.CountDigits(); + + Assert.That(result, Is.EqualTo(expected)); + } + + [Test] + public void CountDigits_ShouldReturn1_GivenNegative1() + { + const short value = -1; + const int expected = 1; + + int result = value.CountDigits(); + + Assert.That(result, Is.EqualTo(expected)); + } + + [Test] + public void CountDigits_ShouldReturn2_Given10() + { + const short value = 10; + const int expected = 2; + + int result = value.CountDigits(); + + Assert.That(result, Is.EqualTo(expected)); + } + + [Test] + public void CountDigits_ShouldReturn3_Given199() + { + const short value = 199; + const int expected = 3; + + int result = value.CountDigits(); + + Assert.That(result, Is.EqualTo(expected)); + } + [Test] public void DigitalRootShouldBeCorrect() { diff --git a/X10D.Tests/src/Math/Int32Tests.cs b/X10D.Tests/src/Math/Int32Tests.cs index e412200..7711d3d 100644 --- a/X10D.Tests/src/Math/Int32Tests.cs +++ b/X10D.Tests/src/Math/Int32Tests.cs @@ -6,6 +6,61 @@ namespace X10D.Tests.Math; [TestFixture] public partial class Int32Tests { + [Test] + public void CountDigits_ShouldReturn1_Given0() + { + const int value = 0; + const int expected = 1; + + int result = value.CountDigits(); + + Assert.That(result, Is.EqualTo(expected)); + } + + [Test] + public void CountDigits_ShouldReturn1_Given1() + { + const int value = 1; + const int expected = 1; + + int result = value.CountDigits(); + + Assert.That(result, Is.EqualTo(expected)); + } + + [Test] + public void CountDigits_ShouldReturn1_GivenNegative1() + { + const int value = -1; + const int expected = 1; + + int result = value.CountDigits(); + + Assert.That(result, Is.EqualTo(expected)); + } + + [Test] + public void CountDigits_ShouldReturn2_Given10() + { + const int value = 10; + const int expected = 2; + + int result = value.CountDigits(); + + Assert.That(result, Is.EqualTo(expected)); + } + + [Test] + public void CountDigits_ShouldReturn3_Given199() + { + const int value = 199; + const int expected = 3; + + int result = value.CountDigits(); + + Assert.That(result, Is.EqualTo(expected)); + } + [Test] public void DigitalRootShouldBeCorrect() { diff --git a/X10D.Tests/src/Math/Int64Tests.cs b/X10D.Tests/src/Math/Int64Tests.cs index de56cd9..557e0b0 100644 --- a/X10D.Tests/src/Math/Int64Tests.cs +++ b/X10D.Tests/src/Math/Int64Tests.cs @@ -6,6 +6,61 @@ namespace X10D.Tests.Math; [TestFixture] public partial class Int64Tests { + [Test] + public void CountDigits_ShouldReturn1_Given0() + { + const long value = 0; + const int expected = 1; + + int result = value.CountDigits(); + + Assert.That(result, Is.EqualTo(expected)); + } + + [Test] + public void CountDigits_ShouldReturn1_Given1() + { + const long value = 1; + const int expected = 1; + + int result = value.CountDigits(); + + Assert.That(result, Is.EqualTo(expected)); + } + + [Test] + public void CountDigits_ShouldReturn1_GivenNegative1() + { + const long value = -1; + const int expected = 1; + + int result = value.CountDigits(); + + Assert.That(result, Is.EqualTo(expected)); + } + + [Test] + public void CountDigits_ShouldReturn2_Given10() + { + const long value = 10; + const int expected = 2; + + int result = value.CountDigits(); + + Assert.That(result, Is.EqualTo(expected)); + } + + [Test] + public void CountDigits_ShouldReturn3_Given199() + { + const long value = 199; + const int expected = 3; + + int result = value.CountDigits(); + + Assert.That(result, Is.EqualTo(expected)); + } + [Test] public void DigitalRootShouldBeCorrect() { diff --git a/X10D.Tests/src/Math/SByteTests.cs b/X10D.Tests/src/Math/SByteTests.cs index 703b1af..454e35f 100644 --- a/X10D.Tests/src/Math/SByteTests.cs +++ b/X10D.Tests/src/Math/SByteTests.cs @@ -7,6 +7,61 @@ namespace X10D.Tests.Math; [CLSCompliant(false)] public partial class SByteTests { + [Test] + public void CountDigits_ShouldReturn1_Given0() + { + const sbyte value = 0; + const int expected = 1; + + int result = value.CountDigits(); + + Assert.That(result, Is.EqualTo(expected)); + } + + [Test] + public void CountDigits_ShouldReturn1_Given1() + { + const sbyte value = 1; + const int expected = 1; + + int result = value.CountDigits(); + + Assert.That(result, Is.EqualTo(expected)); + } + + [Test] + public void CountDigits_ShouldReturn1_GivenNegative1() + { + const sbyte value = -1; + const int expected = 1; + + int result = value.CountDigits(); + + Assert.That(result, Is.EqualTo(expected)); + } + + [Test] + public void CountDigits_ShouldReturn2_Given10() + { + const sbyte value = 10; + const int expected = 2; + + int result = value.CountDigits(); + + Assert.That(result, Is.EqualTo(expected)); + } + + [Test] + public void CountDigits_ShouldReturn3_Given127() + { + const sbyte value = 127; + const int expected = 3; + + int result = value.CountDigits(); + + Assert.That(result, Is.EqualTo(expected)); + } + [Test] public void DigitalRootShouldBeCorrect() { diff --git a/X10D.Tests/src/Math/UInt16Tests.cs b/X10D.Tests/src/Math/UInt16Tests.cs index 7055a00..f123de7 100644 --- a/X10D.Tests/src/Math/UInt16Tests.cs +++ b/X10D.Tests/src/Math/UInt16Tests.cs @@ -7,6 +7,50 @@ namespace X10D.Tests.Math; [CLSCompliant(false)] public partial class UInt16Tests { + [Test] + public void CountDigits_ShouldReturn1_Given0() + { + const ushort value = 0; + const int expected = 1; + + int result = value.CountDigits(); + + Assert.That(result, Is.EqualTo(expected)); + } + + [Test] + public void CountDigits_ShouldReturn1_Given1() + { + const ushort value = 1; + const int expected = 1; + + int result = value.CountDigits(); + + Assert.That(result, Is.EqualTo(expected)); + } + + [Test] + public void CountDigits_ShouldReturn2_Given10() + { + const ushort value = 10; + const int expected = 2; + + int result = value.CountDigits(); + + Assert.That(result, Is.EqualTo(expected)); + } + + [Test] + public void CountDigits_ShouldReturn3_Given199() + { + const ushort value = 199; + const int expected = 3; + + int result = value.CountDigits(); + + Assert.That(result, Is.EqualTo(expected)); + } + [Test] public void DigitalRootShouldBeCorrect() { diff --git a/X10D.Tests/src/Math/UInt32Tests.cs b/X10D.Tests/src/Math/UInt32Tests.cs index b550667..a6c2911 100644 --- a/X10D.Tests/src/Math/UInt32Tests.cs +++ b/X10D.Tests/src/Math/UInt32Tests.cs @@ -7,6 +7,49 @@ namespace X10D.Tests.Math; [CLSCompliant(false)] public partial class UInt32Tests { + [Test] + public void CountDigits_ShouldReturn1_Given0() + { + const uint value = 0; + const int expected = 1; + + int result = value.CountDigits(); + + Assert.That(result, Is.EqualTo(expected)); + } + + [Test] + public void CountDigits_ShouldReturn1_Given1() + { + const uint value = 1; + const int expected = 1; + + int result = value.CountDigits(); + + Assert.That(result, Is.EqualTo(expected)); + } + [Test] + public void CountDigits_ShouldReturn2_Given10() + { + const uint value = 10; + const int expected = 2; + + int result = value.CountDigits(); + + Assert.That(result, Is.EqualTo(expected)); + } + + [Test] + public void CountDigits_ShouldReturn3_Given199() + { + const uint value = 199; + const int expected = 3; + + int result = value.CountDigits(); + + Assert.That(result, Is.EqualTo(expected)); + } + [Test] public void DigitalRootShouldBeCorrect() { diff --git a/X10D.Tests/src/Math/UInt64Tests.cs b/X10D.Tests/src/Math/UInt64Tests.cs index 777c5ab..6d05b4d 100644 --- a/X10D.Tests/src/Math/UInt64Tests.cs +++ b/X10D.Tests/src/Math/UInt64Tests.cs @@ -7,6 +7,50 @@ namespace X10D.Tests.Math; [CLSCompliant(false)] public partial class UInt64Tests { + [Test] + public void CountDigits_ShouldReturn1_Given0() + { + const ulong value = 0; + const int expected = 1; + + int result = value.CountDigits(); + + Assert.That(result, Is.EqualTo(expected)); + } + + [Test] + public void CountDigits_ShouldReturn1_Given1() + { + const ulong value = 1; + const int expected = 1; + + int result = value.CountDigits(); + + Assert.That(result, Is.EqualTo(expected)); + } + + [Test] + public void CountDigits_ShouldReturn2_Given10() + { + const ulong value = 10; + const int expected = 2; + + int result = value.CountDigits(); + + Assert.That(result, Is.EqualTo(expected)); + } + + [Test] + public void CountDigits_ShouldReturn3_Given199() + { + const ulong value = 199; + const int expected = 3; + + int result = value.CountDigits(); + + Assert.That(result, Is.EqualTo(expected)); + } + [Test] public void DigitalRootShouldBeCorrect() { diff --git a/X10D/src/Math/BigIntegerExtensions.cs b/X10D/src/Math/BigIntegerExtensions.cs index 4ce5a0d..31f3884 100644 --- a/X10D/src/Math/BigIntegerExtensions.cs +++ b/X10D/src/Math/BigIntegerExtensions.cs @@ -10,6 +10,21 @@ namespace X10D.Math; /// public static class BigIntegerExtensions { + /// + /// Returns the number of digits in the current integer. + /// + /// The value whose digit count to compute. + /// The number of digits in . + public static int CountDigits(this BigInteger value) + { + if (value == 0) + { + return 1; + } + + return (int)(1 + BigInteger.Log10(BigInteger.Abs(value))); + } + /// /// Computes the digital root of this 8-bit integer. /// diff --git a/X10D/src/Math/ByteExtensions.cs b/X10D/src/Math/ByteExtensions.cs index 6a7db33..bc5a121 100644 --- a/X10D/src/Math/ByteExtensions.cs +++ b/X10D/src/Math/ByteExtensions.cs @@ -9,6 +9,21 @@ namespace X10D.Math; /// public static class ByteExtensions { + /// + /// Returns the number of digits in the current 8-bit unsigned integer. + /// + /// The value whose digit count to compute. + /// The number of digits in . + public static int CountDigits(this byte value) + { + if (value == 0) + { + return 1; + } + + return ((ulong)value).CountDigits(); + } + /// /// Computes the digital root of this 8-bit integer. /// diff --git a/X10D/src/Math/Int16Extensions.cs b/X10D/src/Math/Int16Extensions.cs index 7896cf3..d0d354e 100644 --- a/X10D/src/Math/Int16Extensions.cs +++ b/X10D/src/Math/Int16Extensions.cs @@ -9,6 +9,21 @@ namespace X10D.Math; /// public static class Int16Extensions { + /// + /// Returns the number of digits in the current 16-bit signed integer. + /// + /// The value whose digit count to compute. + /// The number of digits in . + public static int CountDigits(this short value) + { + if (value == 0) + { + return 1; + } + + return ((long)value).CountDigits(); + } + /// /// Computes the digital root of this 16-bit integer. /// diff --git a/X10D/src/Math/Int32Extensions.cs b/X10D/src/Math/Int32Extensions.cs index edaf0dd..95dc664 100644 --- a/X10D/src/Math/Int32Extensions.cs +++ b/X10D/src/Math/Int32Extensions.cs @@ -9,6 +9,21 @@ namespace X10D.Math; /// public static class Int32Extensions { + /// + /// Returns the number of digits in the current 32-bit signed integer. + /// + /// The value whose digit count to compute. + /// The number of digits in . + public static int CountDigits(this int value) + { + if (value == 0) + { + return 1; + } + + return ((long)value).CountDigits(); + } + /// /// Computes the digital root of this 32-bit integer. /// diff --git a/X10D/src/Math/Int64Extensions.cs b/X10D/src/Math/Int64Extensions.cs index 5f888cc..317a379 100644 --- a/X10D/src/Math/Int64Extensions.cs +++ b/X10D/src/Math/Int64Extensions.cs @@ -9,6 +9,21 @@ namespace X10D.Math; /// public static class Int64Extensions { + /// + /// Returns the number of digits in the current 64-bit signed integer. + /// + /// The value whose digit count to compute. + /// The number of digits in . + public static int CountDigits(this long value) + { + if (value == 0) + { + return 1; + } + + return 1 + (int)System.Math.Floor(System.Math.Log10(System.Math.Abs(value))); + } + /// /// Computes the digital root of this 64-bit integer. /// diff --git a/X10D/src/Math/SByteExtensions.cs b/X10D/src/Math/SByteExtensions.cs index cc61037..68dde8d 100644 --- a/X10D/src/Math/SByteExtensions.cs +++ b/X10D/src/Math/SByteExtensions.cs @@ -10,6 +10,21 @@ namespace X10D.Math; [CLSCompliant(false)] public static class SByteExtensions { + /// + /// Returns the number of digits in the current 8-bit signed integer. + /// + /// The value whose digit count to compute. + /// The number of digits in . + public static int CountDigits(this sbyte value) + { + if (value == 0) + { + return 1; + } + + return ((long)value).CountDigits(); + } + /// /// Computes the digital root of this 32-bit integer. /// diff --git a/X10D/src/Math/UInt16Extensions.cs b/X10D/src/Math/UInt16Extensions.cs index e118e58..73b97da 100644 --- a/X10D/src/Math/UInt16Extensions.cs +++ b/X10D/src/Math/UInt16Extensions.cs @@ -10,6 +10,21 @@ namespace X10D.Math; [CLSCompliant(false)] public static class UInt16Extensions { + /// + /// Returns the number of digits in the current 16-bit signed integer. + /// + /// The value whose digit count to compute. + /// The number of digits in . + public static int CountDigits(this ushort value) + { + if (value == 0) + { + return 1; + } + + return ((ulong)value).CountDigits(); + } + /// /// Computes the digital root of the current 16-bit unsigned integer. /// diff --git a/X10D/src/Math/UInt32Extensions.cs b/X10D/src/Math/UInt32Extensions.cs index 79fed45..f74b14c 100644 --- a/X10D/src/Math/UInt32Extensions.cs +++ b/X10D/src/Math/UInt32Extensions.cs @@ -10,6 +10,21 @@ namespace X10D.Math; [CLSCompliant(false)] public static class UInt32Extensions { + /// + /// Returns the number of digits in the current 32-bit unsigned integer. + /// + /// The value whose digit count to compute. + /// The number of digits in . + public static int CountDigits(this uint value) + { + if (value == 0) + { + return 1; + } + + return ((ulong)value).CountDigits(); + } + /// /// Computes the digital root of the current 32-bit unsigned integer. /// diff --git a/X10D/src/Math/UInt64Extensions.cs b/X10D/src/Math/UInt64Extensions.cs index 4bf9365..6c03a98 100644 --- a/X10D/src/Math/UInt64Extensions.cs +++ b/X10D/src/Math/UInt64Extensions.cs @@ -10,6 +10,21 @@ namespace X10D.Math; [CLSCompliant(false)] public static class UInt64Extensions { + /// + /// Returns the number of digits in the current 64-bit unsigned integer. + /// + /// The value whose digit count to compute. + /// The number of digits in . + public static int CountDigits(this ulong value) + { + if (value == 0) + { + return 1; + } + + return 1 + (int)System.Math.Floor(System.Math.Log10(System.Math.Abs((double)value))); + } + /// /// Computes the digital root of the current 64-bit unsigned integer. /// From 5289bd25951ab74e36b0cfd0ee01d592b8a075e4 Mon Sep 17 00:00:00 2001 From: Oliver Booth Date: Thu, 6 Apr 2023 16:03:20 +0100 Subject: [PATCH 2/9] chore: move SourceGenerator to tools folder --- X10D.sln | 3 ++- X10D/X10D.csproj | 2 +- .../SourceGenerator}/EmojiRegexGenerator.cs | 0 .../SourceGenerator/SourceGenerator.csproj | 0 4 files changed, 3 insertions(+), 2 deletions(-) rename {X10D.SourceGenerator => tools/SourceGenerator}/EmojiRegexGenerator.cs (100%) rename X10D.SourceGenerator/X10D.SourceGenerator.csproj => tools/SourceGenerator/SourceGenerator.csproj (100%) diff --git a/X10D.sln b/X10D.sln index 1d8368a..846b992 100644 --- a/X10D.sln +++ b/X10D.sln @@ -22,7 +22,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SourceValidator", "tools\So EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "X10D.Unity", "X10D.Unity\X10D.Unity.csproj", "{7EAB3F09-A9FD-4334-B4DB-0394DD0C6568}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "X10D.SourceGenerator", "X10D.SourceGenerator\X10D.SourceGenerator.csproj", "{077A5D33-AD55-4C55-8A67-972CEBC32C7A}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SourceGenerator", "tools\SourceGenerator\SourceGenerator.csproj", "{077A5D33-AD55-4C55-8A67-972CEBC32C7A}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "X10D.DSharpPlus", "X10D.DSharpPlus\X10D.DSharpPlus.csproj", "{675D3B25-7EA0-4FC3-B513-8DF27874F2CF}" EndProject @@ -100,5 +100,6 @@ Global {84750149-9068-4780-AFDE-CDA1AC57007D} = {4B8969E6-27D2-4357-964E-9979FF7CC805} {CCBF047D-1B01-45EC-8D89-B00B4AC482CA} = {4B8969E6-27D2-4357-964E-9979FF7CC805} {259450A0-9964-403A-91E1-E9111B92C293} = {4B8969E6-27D2-4357-964E-9979FF7CC805} + {077A5D33-AD55-4C55-8A67-972CEBC32C7A} = {4B8969E6-27D2-4357-964E-9979FF7CC805} EndGlobalSection EndGlobal diff --git a/X10D/X10D.csproj b/X10D/X10D.csproj index 14eb676..c886ec5 100644 --- a/X10D/X10D.csproj +++ b/X10D/X10D.csproj @@ -83,7 +83,7 @@ - + diff --git a/X10D.SourceGenerator/EmojiRegexGenerator.cs b/tools/SourceGenerator/EmojiRegexGenerator.cs similarity index 100% rename from X10D.SourceGenerator/EmojiRegexGenerator.cs rename to tools/SourceGenerator/EmojiRegexGenerator.cs diff --git a/X10D.SourceGenerator/X10D.SourceGenerator.csproj b/tools/SourceGenerator/SourceGenerator.csproj similarity index 100% rename from X10D.SourceGenerator/X10D.SourceGenerator.csproj rename to tools/SourceGenerator/SourceGenerator.csproj From 0ca82534025c83a1aab485164ca854e9f5c6ffdc Mon Sep 17 00:00:00 2001 From: Oliver Booth Date: Thu, 6 Apr 2023 16:05:02 +0100 Subject: [PATCH 3/9] chore: remove X10D prefix from sourcegen --- tools/SourceGenerator/EmojiRegexGenerator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/SourceGenerator/EmojiRegexGenerator.cs b/tools/SourceGenerator/EmojiRegexGenerator.cs index 2d67b1d..306bd3f 100644 --- a/tools/SourceGenerator/EmojiRegexGenerator.cs +++ b/tools/SourceGenerator/EmojiRegexGenerator.cs @@ -3,7 +3,7 @@ using System.Text.RegularExpressions; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Text; -namespace X10D.SourceGenerator; +namespace SourceGenerator; [Generator] internal sealed class EmojiRegexGenerator : ISourceGenerator From 172380c57d011f9be4a4eeeef43e333aa67b1032 Mon Sep 17 00:00:00 2001 From: Oliver Booth Date: Thu, 6 Apr 2023 16:58:32 +0100 Subject: [PATCH 4/9] [ci skip] chore: add meta services This class library will contain attributes for future source generation. --- X10D/X10D.csproj | 1 + tools/SourceGenerator/EmojiRegexGenerator.cs | 4 ++-- tools/SourceGenerator/SourceGenerator.csproj | 4 ++++ tools/X10D.MetaServices/X10D.MetaServices.csproj | 10 ++++++++++ 4 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 tools/X10D.MetaServices/X10D.MetaServices.csproj diff --git a/X10D/X10D.csproj b/X10D/X10D.csproj index c886ec5..e034d06 100644 --- a/X10D/X10D.csproj +++ b/X10D/X10D.csproj @@ -84,6 +84,7 @@ + diff --git a/tools/SourceGenerator/EmojiRegexGenerator.cs b/tools/SourceGenerator/EmojiRegexGenerator.cs index 306bd3f..1ac9ca9 100644 --- a/tools/SourceGenerator/EmojiRegexGenerator.cs +++ b/tools/SourceGenerator/EmojiRegexGenerator.cs @@ -47,8 +47,8 @@ internal sealed class EmojiRegexGenerator : ISourceGenerator } var builder = new StringBuilder(); - builder.AppendLine("// This file was auto-generated by X10D.SourceGenerator"); - builder.AppendLine("// Do not edit this file manually"); + builder.AppendLine("// This file was auto-generated by the X10D source generator"); + builder.AppendLine("// Do not edit this file manually!"); builder.AppendLine(); builder.AppendLine("using System.Text.RegularExpressions;"); diff --git a/tools/SourceGenerator/SourceGenerator.csproj b/tools/SourceGenerator/SourceGenerator.csproj index e012f98..266d229 100644 --- a/tools/SourceGenerator/SourceGenerator.csproj +++ b/tools/SourceGenerator/SourceGenerator.csproj @@ -17,4 +17,8 @@ + + + + diff --git a/tools/X10D.MetaServices/X10D.MetaServices.csproj b/tools/X10D.MetaServices/X10D.MetaServices.csproj new file mode 100644 index 0000000..aa845a6 --- /dev/null +++ b/tools/X10D.MetaServices/X10D.MetaServices.csproj @@ -0,0 +1,10 @@ + + + + netstandard2.0 + 11.0 + enable + enable + + + From 7556efdfdd286f385d7a12f03bdf18eef01bad41 Mon Sep 17 00:00:00 2001 From: Oliver Booth Date: Thu, 6 Apr 2023 17:00:39 +0100 Subject: [PATCH 5/9] chore: add AutoOverload and OverloadType attribute --- .../MethodOverloadGenerator.cs | 22 ++++++++++ .../SourceGenerator/OverloadSyntaxReceiver.cs | 41 +++++++++++++++++++ .../AutoOverloadAttribute.cs | 6 +++ .../OverloadTypeAttribute.cs | 20 +++++++++ 4 files changed, 89 insertions(+) create mode 100644 tools/SourceGenerator/MethodOverloadGenerator.cs create mode 100644 tools/SourceGenerator/OverloadSyntaxReceiver.cs create mode 100644 tools/X10D.MetaServices/AutoOverloadAttribute.cs create mode 100644 tools/X10D.MetaServices/OverloadTypeAttribute.cs diff --git a/tools/SourceGenerator/MethodOverloadGenerator.cs b/tools/SourceGenerator/MethodOverloadGenerator.cs new file mode 100644 index 0000000..cf05a35 --- /dev/null +++ b/tools/SourceGenerator/MethodOverloadGenerator.cs @@ -0,0 +1,22 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace SourceGenerator; + +[Generator] +internal sealed class MethodOverloadGenerator : ISourceGenerator +{ + /// + public void Initialize(GeneratorInitializationContext context) + { + context.RegisterForSyntaxNotifications(() => new OverloadSyntaxReceiver()); + } + + /// + public void Execute(GeneratorExecutionContext context) + { + var syntaxReceiver = (OverloadSyntaxReceiver)context.SyntaxReceiver!; + IReadOnlyList candidateMethods = syntaxReceiver.CandidateMethods; + // TODO implement + } +} diff --git a/tools/SourceGenerator/OverloadSyntaxReceiver.cs b/tools/SourceGenerator/OverloadSyntaxReceiver.cs new file mode 100644 index 0000000..cfe4668 --- /dev/null +++ b/tools/SourceGenerator/OverloadSyntaxReceiver.cs @@ -0,0 +1,41 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using X10D.MetaServices; + +namespace SourceGenerator; + +public class OverloadSyntaxReceiver : ISyntaxReceiver +{ + private readonly List _candidateMethods = new(); + + public IReadOnlyList CandidateMethods + { + get => _candidateMethods.AsReadOnly(); + } + + public void OnVisitSyntaxNode(SyntaxNode syntaxNode) + { + if (syntaxNode is not MethodDeclarationSyntax methodDeclarationSyntax) + { + return; + } + + if (methodDeclarationSyntax.AttributeLists.Count == 0) + { + return; + } + + string attributeName = nameof(AutoOverloadAttribute).Replace("Attribute", string.Empty); + foreach (AttributeListSyntax attributeListSyntax in methodDeclarationSyntax.AttributeLists) + { + foreach (AttributeSyntax attributeSyntax in attributeListSyntax.Attributes) + { + if (attributeSyntax.Name.ToString() == attributeName) + { + _candidateMethods.Add(methodDeclarationSyntax); + break; + } + } + } + } +} diff --git a/tools/X10D.MetaServices/AutoOverloadAttribute.cs b/tools/X10D.MetaServices/AutoOverloadAttribute.cs new file mode 100644 index 0000000..5630c84 --- /dev/null +++ b/tools/X10D.MetaServices/AutoOverloadAttribute.cs @@ -0,0 +1,6 @@ +namespace X10D.MetaServices; + +[AttributeUsage(AttributeTargets.Method, Inherited = false)] +public sealed class AutoOverloadAttribute : Attribute +{ +} diff --git a/tools/X10D.MetaServices/OverloadTypeAttribute.cs b/tools/X10D.MetaServices/OverloadTypeAttribute.cs new file mode 100644 index 0000000..d938a5b --- /dev/null +++ b/tools/X10D.MetaServices/OverloadTypeAttribute.cs @@ -0,0 +1,20 @@ +namespace X10D.MetaServices; + +[AttributeUsage(AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.ReturnValue, Inherited = false)] +public sealed class OverloadTypeAttribute : Attribute +{ + /// + /// Initializes a new instance of the class. + /// + /// The types to overload. + public OverloadTypeAttribute(params Type[] types) + { + Types = (Type[])types.Clone(); + } + + /// + /// Gets an array of types to overload. + /// + /// An array of types to overload. + public Type[] Types { get; } +} From ce35f8676e24fd6a2d2496ab07310ff85bd3b6ee Mon Sep 17 00:00:00 2001 From: Oliver Booth Date: Thu, 6 Apr 2023 17:05:37 +0100 Subject: [PATCH 6/9] chore: make attributes internal --- tools/X10D.MetaServices/Assembly.cs | 4 ++++ tools/X10D.MetaServices/AutoOverloadAttribute.cs | 2 +- tools/X10D.MetaServices/OverloadTypeAttribute.cs | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) create mode 100644 tools/X10D.MetaServices/Assembly.cs diff --git a/tools/X10D.MetaServices/Assembly.cs b/tools/X10D.MetaServices/Assembly.cs new file mode 100644 index 0000000..274a45a --- /dev/null +++ b/tools/X10D.MetaServices/Assembly.cs @@ -0,0 +1,4 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("X10D")] +[assembly: InternalsVisibleTo("SourceGenerator")] diff --git a/tools/X10D.MetaServices/AutoOverloadAttribute.cs b/tools/X10D.MetaServices/AutoOverloadAttribute.cs index 5630c84..382e63e 100644 --- a/tools/X10D.MetaServices/AutoOverloadAttribute.cs +++ b/tools/X10D.MetaServices/AutoOverloadAttribute.cs @@ -1,6 +1,6 @@ namespace X10D.MetaServices; [AttributeUsage(AttributeTargets.Method, Inherited = false)] -public sealed class AutoOverloadAttribute : Attribute +internal sealed class AutoOverloadAttribute : Attribute { } diff --git a/tools/X10D.MetaServices/OverloadTypeAttribute.cs b/tools/X10D.MetaServices/OverloadTypeAttribute.cs index d938a5b..0fdde1a 100644 --- a/tools/X10D.MetaServices/OverloadTypeAttribute.cs +++ b/tools/X10D.MetaServices/OverloadTypeAttribute.cs @@ -1,7 +1,7 @@ namespace X10D.MetaServices; [AttributeUsage(AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.ReturnValue, Inherited = false)] -public sealed class OverloadTypeAttribute : Attribute +internal sealed class OverloadTypeAttribute : Attribute { /// /// Initializes a new instance of the class. From 9d26f3da60dab8d07d07ecc29e0ca3c4cc12f160 Mon Sep 17 00:00:00 2001 From: Oliver Booth Date: Thu, 6 Apr 2023 19:11:54 +0100 Subject: [PATCH 7/9] feat: add TextWriter.WriteNoAlloc/WriteLineNoAlloc Allows writing of integer types without allocating a string. --- CHANGELOG.md | 8 + X10D.Tests/src/IO/TextWriterTests.Int32.cs | 131 ++++ X10D.Tests/src/IO/TextWriterTests.Int64.cs | 131 ++++ X10D.Tests/src/IO/TextWriterTests.UInt32.cs | 131 ++++ X10D.Tests/src/IO/TextWriterTests.UInt64.cs | 131 ++++ X10D.Tests/src/IO/TextWriterTests.cs | 46 ++ X10D/src/IO/TextWriterExtensions.cs | 685 ++++++++++++++++++++ 7 files changed, 1263 insertions(+) create mode 100644 X10D.Tests/src/IO/TextWriterTests.Int32.cs create mode 100644 X10D.Tests/src/IO/TextWriterTests.Int64.cs create mode 100644 X10D.Tests/src/IO/TextWriterTests.UInt32.cs create mode 100644 X10D.Tests/src/IO/TextWriterTests.UInt64.cs create mode 100644 X10D.Tests/src/IO/TextWriterTests.cs create mode 100644 X10D/src/IO/TextWriterExtensions.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index e7daff2..4de5308 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - X10D: Added math-related extension methods for `BigInteger`. - X10D: Added `Span.Replace(T, T)`. - X10D: Added `CountDigits` for integer types. +- X10D: Added `TextWriter.WriteNoAlloc(int[, ReadOnlySpan[, IFormatProvider]])`. +- X10D: Added `TextWriter.WriteNoAlloc(uint[, ReadOnlySpan[, IFormatProvider]])`. +- X10D: Added `TextWriter.WriteNoAlloc(long[, ReadOnlySpan[, IFormatProvider]])`. +- X10D: Added `TextWriter.WriteNoAlloc(ulong[, ReadOnlySpan[, IFormatProvider]])`. +- X10D: Added `TextWriter.WriteLineNoAlloc(int[, ReadOnlySpan[, IFormatProvider]])`. +- X10D: Added `TextWriter.WriteLineNoAlloc(uint[, ReadOnlySpan[, IFormatProvider]])`. +- X10D: Added `TextWriter.WriteLineNoAlloc(long[, ReadOnlySpan[, IFormatProvider]])`. +- X10D: Added `TextWriter.WriteLineNoAlloc(ulong[, ReadOnlySpan[, IFormatProvider]])`. ### Changed - X10D: `DateTime.Age(DateTime)` and `DateTimeOffset.Age(DateTimeOffset)` parameter renamed from `asOf` to `referenceDate`. diff --git a/X10D.Tests/src/IO/TextWriterTests.Int32.cs b/X10D.Tests/src/IO/TextWriterTests.Int32.cs new file mode 100644 index 0000000..31bed11 --- /dev/null +++ b/X10D.Tests/src/IO/TextWriterTests.Int32.cs @@ -0,0 +1,131 @@ +using System.Globalization; +using System.Text; +using NUnit.Framework; +using X10D.IO; + +namespace X10D.Tests.IO; + +public partial class TextWriterTests +{ + [Test] + public void WriteNoAlloc_ShouldThrowArgumentNullException_GivenInt32_AndNullWriter() + { + TextWriter writer = null!; + Assert.Throws(() => writer.WriteNoAlloc(420)); + Assert.Throws(() => writer.WriteNoAlloc(420, "N0")); + Assert.Throws(() => writer.WriteNoAlloc(420, "N0", null)); + } + + [Test] + public void WriteNoAlloc_ShouldThrowObjectDisposedException_GivenInt32_AndDisposedStream() + { + var stream = new MemoryStream(); + var writer = new StreamWriter(stream, Encoding.UTF8); + writer.Dispose(); + stream.Dispose(); + + Assert.Throws(() => writer.WriteNoAlloc(420)); + Assert.Throws(() => writer.WriteNoAlloc(420, "N0")); + Assert.Throws(() => writer.WriteNoAlloc(420, "N0", null)); + } + + [Test] + public void WriteLineNoAlloc_ShouldThrowArgumentNullException_GivenInt32_AndNullWriter() + { + TextWriter writer = null!; + Assert.Throws(() => writer.WriteLineNoAlloc(420)); + Assert.Throws(() => writer.WriteLineNoAlloc(420, "N0")); + Assert.Throws(() => writer.WriteLineNoAlloc(420, "N0", null)); + } + + [Test] + public void WriteLineNoAlloc_ShouldThrowObjectDisposedException_GivenInt32_AndDisposedStream() + { + var stream = new MemoryStream(); + var writer = new StreamWriter(stream, Encoding.UTF8); + writer.Dispose(); + stream.Dispose(); + + Assert.Throws(() => writer.WriteLineNoAlloc(420)); + Assert.Throws(() => writer.WriteLineNoAlloc(420, "N0")); + Assert.Throws(() => writer.WriteLineNoAlloc(420, "N0", null)); + } + + [Test] + public void WriteNoAlloc_ShouldWriteTextValue_GivenInt32() + { + Assert.That(_stream.Length, Is.Zero); + _writer.WriteNoAlloc(420); + _writer.Flush(); + + string actual = Encoding.UTF8.GetString(_stream.ToArray()); + const string expected = "420"; + + Assert.That(actual, Is.EqualTo(expected)); + } + + [Test] + public void WriteNoAlloc_ShouldWriteTextValue_GivenInt32_AndFormatString() + { + Assert.That(_stream.Length, Is.Zero); + _writer.WriteNoAlloc(420, "N0"); + _writer.Flush(); + + string actual = Encoding.UTF8.GetString(_stream.ToArray()); + const string expected = "420"; + + Assert.That(actual, Is.EqualTo(expected)); + } + + [Test] + public void WriteNoAlloc_ShouldWriteTextValue_GivenInt32_AndFormatString_AndCultureInfo() + { + Assert.That(_stream.Length, Is.Zero); + _writer.WriteNoAlloc(420, "N0", CultureInfo.CurrentCulture); + _writer.Flush(); + + string actual = Encoding.UTF8.GetString(_stream.ToArray()); + const string expected = "420"; + + Assert.That(actual, Is.EqualTo(expected)); + } + + [Test] + public void WriteLineNoAlloc_ShouldWriteTextValue_GivenInt32() + { + Assert.That(_stream.Length, Is.Zero); + _writer.WriteLineNoAlloc(420); + _writer.Flush(); + + string actual = Encoding.UTF8.GetString(_stream.ToArray()); + var expected = $"420{Environment.NewLine}"; + + Assert.That(actual, Is.EqualTo(expected)); + } + + [Test] + public void WriteLineNoAlloc_ShouldWriteTextValue_GivenInt32_AndFormatString() + { + Assert.That(_stream.Length, Is.Zero); + _writer.WriteLineNoAlloc(420, "N0"); + _writer.Flush(); + + string actual = Encoding.UTF8.GetString(_stream.ToArray()); + var expected = $"420{Environment.NewLine}"; + + Assert.That(actual, Is.EqualTo(expected)); + } + + [Test] + public void WriteLineNoAlloc_ShouldWriteTextValue_GivenInt32_AndFormatString_AndCultureInfo() + { + Assert.That(_stream.Length, Is.Zero); + _writer.WriteLineNoAlloc(420, "N0", CultureInfo.CurrentCulture); + _writer.Flush(); + + string actual = Encoding.UTF8.GetString(_stream.ToArray()); + var expected = $"420{Environment.NewLine}"; + + Assert.That(actual, Is.EqualTo(expected)); + } +} diff --git a/X10D.Tests/src/IO/TextWriterTests.Int64.cs b/X10D.Tests/src/IO/TextWriterTests.Int64.cs new file mode 100644 index 0000000..affd830 --- /dev/null +++ b/X10D.Tests/src/IO/TextWriterTests.Int64.cs @@ -0,0 +1,131 @@ +using System.Globalization; +using System.Text; +using NUnit.Framework; +using X10D.IO; + +namespace X10D.Tests.IO; + +public partial class TextWriterTests +{ + [Test] + public void WriteNoAlloc_ShouldThrowArgumentNullException_GivenInt64_AndNullWriter() + { + TextWriter writer = null!; + Assert.Throws(() => writer.WriteNoAlloc(420L)); + Assert.Throws(() => writer.WriteNoAlloc(420L, "N0")); + Assert.Throws(() => writer.WriteNoAlloc(420L, "N0", null)); + } + + [Test] + public void WriteNoAlloc_ShouldThrowObjectDisposedException_GivenInt64_AndDisposedStream() + { + var stream = new MemoryStream(); + var writer = new StreamWriter(stream, Encoding.UTF8); + writer.Dispose(); + stream.Dispose(); + + Assert.Throws(() => writer.WriteNoAlloc(420L)); + Assert.Throws(() => writer.WriteNoAlloc(420L, "N0")); + Assert.Throws(() => writer.WriteNoAlloc(420L, "N0", null)); + } + + [Test] + public void WriteLineNoAlloc_ShouldThrowArgumentNullException_GivenInt64_AndNullWriter() + { + TextWriter writer = null!; + Assert.Throws(() => writer.WriteLineNoAlloc(420L)); + Assert.Throws(() => writer.WriteLineNoAlloc(420L, "N0")); + Assert.Throws(() => writer.WriteLineNoAlloc(420L, "N0", null)); + } + + [Test] + public void WriteLineNoAlloc_ShouldThrowObjectDisposedException_GivenInt64_AndDisposedStream() + { + var stream = new MemoryStream(); + var writer = new StreamWriter(stream, Encoding.UTF8); + writer.Dispose(); + stream.Dispose(); + + Assert.Throws(() => writer.WriteLineNoAlloc(420L)); + Assert.Throws(() => writer.WriteLineNoAlloc(420L, "N0")); + Assert.Throws(() => writer.WriteLineNoAlloc(420L, "N0", null)); + } + + [Test] + public void WriteNoAlloc_ShouldWriteTextValue_GivenInt64() + { + Assert.That(_stream.Length, Is.Zero); + _writer.WriteNoAlloc(420L); + _writer.Flush(); + + string actual = Encoding.UTF8.GetString(_stream.ToArray()); + const string expected = "420"; + + Assert.That(actual, Is.EqualTo(expected)); + } + + [Test] + public void WriteNoAlloc_ShouldWriteTextValue_GivenInt64_AndFormatString() + { + Assert.That(_stream.Length, Is.Zero); + _writer.WriteNoAlloc(420L, "N0"); + _writer.Flush(); + + string actual = Encoding.UTF8.GetString(_stream.ToArray()); + const string expected = "420"; + + Assert.That(actual, Is.EqualTo(expected)); + } + + [Test] + public void WriteNoAlloc_ShouldWriteTextValue_GivenInt64_AndFormatString_AndCultureInfo() + { + Assert.That(_stream.Length, Is.Zero); + _writer.WriteNoAlloc(420L, "N0", CultureInfo.CurrentCulture); + _writer.Flush(); + + string actual = Encoding.UTF8.GetString(_stream.ToArray()); + const string expected = "420"; + + Assert.That(actual, Is.EqualTo(expected)); + } + + [Test] + public void WriteLineNoAlloc_ShouldWriteTextValue_GivenInt64() + { + Assert.That(_stream.Length, Is.Zero); + _writer.WriteLineNoAlloc(420L); + _writer.Flush(); + + string actual = Encoding.UTF8.GetString(_stream.ToArray()); + var expected = $"420{Environment.NewLine}"; + + Assert.That(actual, Is.EqualTo(expected)); + } + + [Test] + public void WriteLineNoAlloc_ShouldWriteTextValue_GivenInt64_AndFormatString() + { + Assert.That(_stream.Length, Is.Zero); + _writer.WriteLineNoAlloc(420L, "N0"); + _writer.Flush(); + + string actual = Encoding.UTF8.GetString(_stream.ToArray()); + var expected = $"420{Environment.NewLine}"; + + Assert.That(actual, Is.EqualTo(expected)); + } + + [Test] + public void WriteLineNoAlloc_ShouldWriteTextValue_GivenInt64_AndFormatString_AndCultureInfo() + { + Assert.That(_stream.Length, Is.Zero); + _writer.WriteLineNoAlloc(420L, "N0", CultureInfo.CurrentCulture); + _writer.Flush(); + + string actual = Encoding.UTF8.GetString(_stream.ToArray()); + var expected = $"420{Environment.NewLine}"; + + Assert.That(actual, Is.EqualTo(expected)); + } +} diff --git a/X10D.Tests/src/IO/TextWriterTests.UInt32.cs b/X10D.Tests/src/IO/TextWriterTests.UInt32.cs new file mode 100644 index 0000000..8aab080 --- /dev/null +++ b/X10D.Tests/src/IO/TextWriterTests.UInt32.cs @@ -0,0 +1,131 @@ +using System.Globalization; +using System.Text; +using NUnit.Framework; +using X10D.IO; + +namespace X10D.Tests.IO; + +public partial class TextWriterTests +{ + [Test] + public void WriteNoAlloc_ShouldThrowArgumentNullException_GivenUInt32_AndNullWriter() + { + TextWriter writer = null!; + Assert.Throws(() => writer.WriteNoAlloc(420U)); + Assert.Throws(() => writer.WriteNoAlloc(420U, "N0")); + Assert.Throws(() => writer.WriteNoAlloc(420U, "N0", null)); + } + + [Test] + public void WriteNoAlloc_ShouldThrowObjectDisposedException_GivenUInt32_AndDisposedStream() + { + var stream = new MemoryStream(); + var writer = new StreamWriter(stream, Encoding.UTF8); + writer.Dispose(); + stream.Dispose(); + + Assert.Throws(() => writer.WriteNoAlloc(420U)); + Assert.Throws(() => writer.WriteNoAlloc(420U, "N0")); + Assert.Throws(() => writer.WriteNoAlloc(420U, "N0", null)); + } + + [Test] + public void WriteLineNoAlloc_ShouldThrowArgumentNullException_GivenUInt32_AndNullWriter() + { + TextWriter writer = null!; + Assert.Throws(() => writer.WriteLineNoAlloc(420U)); + Assert.Throws(() => writer.WriteLineNoAlloc(420U, "N0")); + Assert.Throws(() => writer.WriteLineNoAlloc(420U, "N0", null)); + } + + [Test] + public void WriteLineNoAlloc_ShouldThrowObjectDisposedException_GivenUInt32_AndDisposedStream() + { + var stream = new MemoryStream(); + var writer = new StreamWriter(stream, Encoding.UTF8); + writer.Dispose(); + stream.Dispose(); + + Assert.Throws(() => writer.WriteLineNoAlloc(420U)); + Assert.Throws(() => writer.WriteLineNoAlloc(420U, "N0")); + Assert.Throws(() => writer.WriteLineNoAlloc(420U, "N0", null)); + } + + [Test] + public void WriteNoAlloc_ShouldWriteTextValue_GivenUInt32() + { + Assert.That(_stream.Length, Is.Zero); + _writer.WriteNoAlloc(420U); + _writer.Flush(); + + string actual = Encoding.UTF8.GetString(_stream.ToArray()); + const string expected = "420"; + + Assert.That(actual, Is.EqualTo(expected)); + } + + [Test] + public void WriteNoAlloc_ShouldWriteTextValue_GivenUInt32_AndFormatString() + { + Assert.That(_stream.Length, Is.Zero); + _writer.WriteNoAlloc(420U, "N0"); + _writer.Flush(); + + string actual = Encoding.UTF8.GetString(_stream.ToArray()); + const string expected = "420"; + + Assert.That(actual, Is.EqualTo(expected)); + } + + [Test] + public void WriteNoAlloc_ShouldWriteTextValue_GivenUInt32_AndFormatString_AndCultureInfo() + { + Assert.That(_stream.Length, Is.Zero); + _writer.WriteNoAlloc(420U, "N0", CultureInfo.CurrentCulture); + _writer.Flush(); + + string actual = Encoding.UTF8.GetString(_stream.ToArray()); + const string expected = "420"; + + Assert.That(actual, Is.EqualTo(expected)); + } + + [Test] + public void WriteLineNoAlloc_ShouldWriteTextValue_GivenUInt32() + { + Assert.That(_stream.Length, Is.Zero); + _writer.WriteLineNoAlloc(420U); + _writer.Flush(); + + string actual = Encoding.UTF8.GetString(_stream.ToArray()); + var expected = $"420{Environment.NewLine}"; + + Assert.That(actual, Is.EqualTo(expected)); + } + + [Test] + public void WriteLineNoAlloc_ShouldWriteTextValue_GivenUInt32_AndFormatString() + { + Assert.That(_stream.Length, Is.Zero); + _writer.WriteLineNoAlloc(420U, "N0"); + _writer.Flush(); + + string actual = Encoding.UTF8.GetString(_stream.ToArray()); + var expected = $"420{Environment.NewLine}"; + + Assert.That(actual, Is.EqualTo(expected)); + } + + [Test] + public void WriteLineNoAlloc_ShouldWriteTextValue_GivenUInt32_AndFormatString_AndCultureInfo() + { + Assert.That(_stream.Length, Is.Zero); + _writer.WriteLineNoAlloc(420U, "N0", CultureInfo.CurrentCulture); + _writer.Flush(); + + string actual = Encoding.UTF8.GetString(_stream.ToArray()); + var expected = $"420{Environment.NewLine}"; + + Assert.That(actual, Is.EqualTo(expected)); + } +} diff --git a/X10D.Tests/src/IO/TextWriterTests.UInt64.cs b/X10D.Tests/src/IO/TextWriterTests.UInt64.cs new file mode 100644 index 0000000..57f5425 --- /dev/null +++ b/X10D.Tests/src/IO/TextWriterTests.UInt64.cs @@ -0,0 +1,131 @@ +using System.Globalization; +using System.Text; +using NUnit.Framework; +using X10D.IO; + +namespace X10D.Tests.IO; + +public partial class TextWriterTests +{ + [Test] + public void WriteNoAlloc_ShouldThrowArgumentNullException_GivenUInt64_AndNullWriter() + { + TextWriter writer = null!; + Assert.Throws(() => writer.WriteNoAlloc(420UL)); + Assert.Throws(() => writer.WriteNoAlloc(420UL, "N0")); + Assert.Throws(() => writer.WriteNoAlloc(420UL, "N0", null)); + } + + [Test] + public void WriteNoAlloc_ShouldThrowObjectDisposedException_GivenUInt64_AndDisposedStream() + { + var stream = new MemoryStream(); + var writer = new StreamWriter(stream, Encoding.UTF8); + writer.Dispose(); + stream.Dispose(); + + Assert.Throws(() => writer.WriteNoAlloc(420UL)); + Assert.Throws(() => writer.WriteNoAlloc(420UL, "N0")); + Assert.Throws(() => writer.WriteNoAlloc(420UL, "N0", null)); + } + + [Test] + public void WriteLineNoAlloc_ShouldThrowArgumentNullException_GivenUInt64_AndNullWriter() + { + TextWriter writer = null!; + Assert.Throws(() => writer.WriteLineNoAlloc(420UL)); + Assert.Throws(() => writer.WriteLineNoAlloc(420UL, "N0")); + Assert.Throws(() => writer.WriteLineNoAlloc(420UL, "N0", null)); + } + + [Test] + public void WriteLineNoAlloc_ShouldThrowObjectDisposedException_GivenUInt64_AndDisposedStream() + { + var stream = new MemoryStream(); + var writer = new StreamWriter(stream, Encoding.UTF8); + writer.Dispose(); + stream.Dispose(); + + Assert.Throws(() => writer.WriteLineNoAlloc(420UL)); + Assert.Throws(() => writer.WriteLineNoAlloc(420UL, "N0")); + Assert.Throws(() => writer.WriteLineNoAlloc(420UL, "N0", null)); + } + + [Test] + public void WriteNoAlloc_ShouldWriteTextValue_GivenUInt64() + { + Assert.That(_stream.Length, Is.Zero); + _writer.WriteNoAlloc(420UL); + _writer.Flush(); + + string actual = Encoding.UTF8.GetString(_stream.ToArray()); + const string expected = "420"; + + Assert.That(actual, Is.EqualTo(expected)); + } + + [Test] + public void WriteNoAlloc_ShouldWriteTextValue_GivenUInt64_AndFormatString() + { + Assert.That(_stream.Length, Is.Zero); + _writer.WriteNoAlloc(420UL, "N0"); + _writer.Flush(); + + string actual = Encoding.UTF8.GetString(_stream.ToArray()); + const string expected = "420"; + + Assert.That(actual, Is.EqualTo(expected)); + } + + [Test] + public void WriteNoAlloc_ShouldWriteTextValue_GivenUInt64_AndFormatString_AndCultureInfo() + { + Assert.That(_stream.Length, Is.Zero); + _writer.WriteNoAlloc(420UL, "N0", CultureInfo.CurrentCulture); + _writer.Flush(); + + string actual = Encoding.UTF8.GetString(_stream.ToArray()); + const string expected = "420"; + + Assert.That(actual, Is.EqualTo(expected)); + } + + [Test] + public void WriteLineNoAlloc_ShouldWriteTextValue_GivenUInt64() + { + Assert.That(_stream.Length, Is.Zero); + _writer.WriteLineNoAlloc(420UL); + _writer.Flush(); + + string actual = Encoding.UTF8.GetString(_stream.ToArray()); + var expected = $"420{Environment.NewLine}"; + + Assert.That(actual, Is.EqualTo(expected)); + } + + [Test] + public void WriteLineNoAlloc_ShouldWriteTextValue_GivenUInt64_AndFormatString() + { + Assert.That(_stream.Length, Is.Zero); + _writer.WriteLineNoAlloc(420UL, "N0"); + _writer.Flush(); + + string actual = Encoding.UTF8.GetString(_stream.ToArray()); + var expected = $"420{Environment.NewLine}"; + + Assert.That(actual, Is.EqualTo(expected)); + } + + [Test] + public void WriteLineNoAlloc_ShouldWriteTextValue_GivenUInt64_AndFormatString_AndCultureInfo() + { + Assert.That(_stream.Length, Is.Zero); + _writer.WriteLineNoAlloc(420UL, "N0", CultureInfo.CurrentCulture); + _writer.Flush(); + + string actual = Encoding.UTF8.GetString(_stream.ToArray()); + var expected = $"420{Environment.NewLine}"; + + Assert.That(actual, Is.EqualTo(expected)); + } +} diff --git a/X10D.Tests/src/IO/TextWriterTests.cs b/X10D.Tests/src/IO/TextWriterTests.cs new file mode 100644 index 0000000..b98268a --- /dev/null +++ b/X10D.Tests/src/IO/TextWriterTests.cs @@ -0,0 +1,46 @@ +using System.Diagnostics; +using System.Text; +using NUnit.Framework; + +namespace X10D.Tests.IO; + +[TestFixture] +public partial class TextWriterTests +{ + private MemoryStream _stream = null!; + private StreamWriter _writer = null!; + + [OneTimeSetUp] + public void OneTimeSetup() + { + _stream = new MemoryStream(); + _writer = new StreamWriter(_stream, Encoding.UTF8); + _writer.Write(' '); + _writer.Flush(); + _stream.SetLength(0); + _stream.Position = 0; + + Trace.Listeners.Add(new ConsoleTraceListener()); + } + + [OneTimeTearDown] + public void OneTimeTearDown() + { + _writer.Dispose(); + _stream.Dispose(); + + Trace.Flush(); + } + + [SetUp] + public void Setup() + { + _stream.SetLength(0); + } + + [TearDown] + public void TearDown() + { + _stream.SetLength(0); + } +} diff --git a/X10D/src/IO/TextWriterExtensions.cs b/X10D/src/IO/TextWriterExtensions.cs new file mode 100644 index 0000000..7d1b3c5 --- /dev/null +++ b/X10D/src/IO/TextWriterExtensions.cs @@ -0,0 +1,685 @@ +using System.Globalization; +using X10D.Math; + +namespace X10D.IO; + +/// +/// IO-related extension methods for . +/// +public static class TextWriterExtensions +{ + /// + /// Writes the text representation of a 4-byte signed integer to the text stream, without allocating a string. + /// + /// The to write to. + /// The 4-byte signed integer to write. + /// This method may still allocate if the integer is too large to fit in a stack-allocated buffer. + /// is . + /// The is closed. + /// An I/O error occurs. + public static void WriteNoAlloc(this TextWriter writer, int value) + { +#if NET6_0_OR_GREATER + ArgumentNullException.ThrowIfNull(writer); +#else + if (writer is null) + { + throw new ArgumentNullException(nameof(writer)); + } +#endif + + WriteNoAlloc(writer, value, "N0".AsSpan(), CultureInfo.CurrentCulture); + } + + /// + /// Writes the text representation of a 4-byte signed integer to the text stream, without allocating a string. + /// + /// The to write to. + /// The 4-byte signed integer to write. + /// A standard or custom numeric format string. + /// This method may still allocate if the integer is too large to fit in a stack-allocated buffer. + /// is . + /// The is closed. + /// An I/O error occurs. + public static void WriteNoAlloc(this TextWriter writer, int value, ReadOnlySpan format) + { +#if NET6_0_OR_GREATER + ArgumentNullException.ThrowIfNull(writer); +#else + if (writer is null) + { + throw new ArgumentNullException(nameof(writer)); + } +#endif + + WriteNoAlloc(writer, value, format, CultureInfo.CurrentCulture); + } + + /// + /// Writes the text representation of a 4-byte signed integer to the text stream, without allocating a string. + /// + /// The to write to. + /// The 4-byte signed integer to write. + /// A standard or custom numeric format string. + /// An object that supplies culture-specific formatting information. + /// This method may still allocate if the integer is too large to fit in a stack-allocated buffer. + /// is . + /// The is closed. + /// An I/O error occurs. + public static void WriteNoAlloc(this TextWriter writer, int value, ReadOnlySpan format, IFormatProvider? formatProvider) + { +#if NET6_0_OR_GREATER + ArgumentNullException.ThrowIfNull(writer); +#else + if (writer is null) + { + throw new ArgumentNullException(nameof(writer)); + } +#endif + + int digitCount = value.CountDigits(); + Span buffer = stackalloc char[System.Math.Max(value < 0 ? digitCount + 1 : digitCount, 1000)]; + if (value.TryFormat(buffer, out int charsWritten, format, formatProvider)) + { + Span truncated = buffer[..charsWritten]; + for (var index = 0; index < truncated.Length; index++) + { + writer.Write(truncated[index]); + } + } + else + { + writer.Write(value.ToString(format.ToString(), formatProvider)); + } + } + + /// + /// Writes the text representation of a 4-byte unsigned integer to the text stream, without allocating a string. + /// + /// The to write to. + /// The 4-byte unsigned integer to write. + /// This method may still allocate if the integer is too large to fit in a stack-allocated buffer. + /// is . + /// The is closed. + /// An I/O error occurs. + [CLSCompliant(false)] + public static void WriteNoAlloc(this TextWriter writer, uint value) + { +#if NET6_0_OR_GREATER + ArgumentNullException.ThrowIfNull(writer); +#else + if (writer is null) + { + throw new ArgumentNullException(nameof(writer)); + } +#endif + + WriteNoAlloc(writer, value, "N0".AsSpan(), CultureInfo.CurrentCulture); + } + + /// + /// Writes the text representation of a 4-byte unsigned integer to the text stream, without allocating a string. + /// + /// The to write to. + /// The 4-byte unsigned integer to write. + /// A standard or custom numeric format string. + /// This method may still allocate if the integer is too large to fit in a stack-allocated buffer. + /// is . + /// The is closed. + /// An I/O error occurs. + [CLSCompliant(false)] + public static void WriteNoAlloc(this TextWriter writer, uint value, ReadOnlySpan format) + { +#if NET6_0_OR_GREATER + ArgumentNullException.ThrowIfNull(writer); +#else + if (writer is null) + { + throw new ArgumentNullException(nameof(writer)); + } +#endif + + WriteNoAlloc(writer, value, format, CultureInfo.CurrentCulture); + } + + /// + /// Writes the text representation of a 4-byte unsigned integer to the text stream, without allocating a string. + /// + /// The to write to. + /// The 4-byte unsigned integer to write. + /// A standard or custom numeric format string. + /// An object that supplies culture-specific formatting information. + /// This method may still allocate if the integer is too large to fit in a stack-allocated buffer. + /// is . + /// The is closed. + /// An I/O error occurs. + [CLSCompliant(false)] + public static void WriteNoAlloc(this TextWriter writer, + uint value, + ReadOnlySpan format, + IFormatProvider? formatProvider) + { +#if NET6_0_OR_GREATER + ArgumentNullException.ThrowIfNull(writer); +#else + if (writer is null) + { + throw new ArgumentNullException(nameof(writer)); + } +#endif + + int digitCount = value.CountDigits(); + Span buffer = stackalloc char[System.Math.Max(digitCount, 1000)]; + if (value.TryFormat(buffer, out int charsWritten, format, formatProvider)) + { + Span truncated = buffer[..charsWritten]; + for (var index = 0; index < truncated.Length; index++) + { + writer.Write(truncated[index]); + } + } + else + { + writer.Write(value.ToString(format.ToString(), formatProvider)); + } + } + + /// + /// Writes the text representation of a 8-byte signed integer to the text stream, without allocating a string. + /// + /// The to write to. + /// The 8-byte signed integer to write. + /// This method may still allocate if the integer is too large to fit in a stack-allocated buffer. + /// is . + /// The is closed. + /// An I/O error occurs. + public static void WriteNoAlloc(this TextWriter writer, long value) + { +#if NET6_0_OR_GREATER + ArgumentNullException.ThrowIfNull(writer); +#else + if (writer is null) + { + throw new ArgumentNullException(nameof(writer)); + } +#endif + + WriteNoAlloc(writer, value, "N0".AsSpan(), CultureInfo.CurrentCulture); + } + + /// + /// Writes the text representation of a 8-byte signed integer to the text stream, without allocating a string. + /// + /// The to write to. + /// The 8-byte signed integer to write. + /// A standard or custom numeric format string. + /// This method may still allocate if the integer is too large to fit in a stack-allocated buffer. + /// is . + /// The is closed. + /// An I/O error occurs. + public static void WriteNoAlloc(this TextWriter writer, long value, ReadOnlySpan format) + { +#if NET6_0_OR_GREATER + ArgumentNullException.ThrowIfNull(writer); +#else + if (writer is null) + { + throw new ArgumentNullException(nameof(writer)); + } +#endif + + WriteNoAlloc(writer, value, format, CultureInfo.CurrentCulture); + } + + /// + /// Writes the text representation of a 8-byte signed integer to the text stream, without allocating a string. + /// + /// The to write to. + /// The 8-byte signed integer to write. + /// A standard or custom numeric format string. + /// An object that supplies culture-specific formatting information. + /// This method may still allocate if the integer is too large to fit in a stack-allocated buffer. + /// is . + /// The is closed. + /// An I/O error occurs. + public static void WriteNoAlloc(this TextWriter writer, + long value, + ReadOnlySpan format, + IFormatProvider? formatProvider) + { +#if NET6_0_OR_GREATER + ArgumentNullException.ThrowIfNull(writer); +#else + if (writer is null) + { + throw new ArgumentNullException(nameof(writer)); + } +#endif + + int digitCount = value.CountDigits(); + Span buffer = stackalloc char[System.Math.Max(value < 0 ? digitCount + 1 : digitCount, 1000)]; + if (value.TryFormat(buffer, out int charsWritten, format, formatProvider)) + { + Span truncated = buffer[..charsWritten]; + for (var index = 0; index < truncated.Length; index++) + { + writer.Write(truncated[index]); + } + } + else + { + writer.Write(value.ToString(format.ToString(), formatProvider)); + } + } + + /// + /// Writes the text representation of a 8-byte unsigned integer to the text stream, without allocating a string. + /// + /// The to write to. + /// The 8-byte unsigned integer to write. + /// This method may still allocate if the integer is too large to fit in a stack-allocated buffer. + /// is . + /// The is closed. + /// An I/O error occurs. + [CLSCompliant(false)] + public static void WriteNoAlloc(this TextWriter writer, ulong value) + { +#if NET6_0_OR_GREATER + ArgumentNullException.ThrowIfNull(writer); +#else + if (writer is null) + { + throw new ArgumentNullException(nameof(writer)); + } +#endif + + WriteNoAlloc(writer, value, "N0".AsSpan(), CultureInfo.CurrentCulture); + } + + /// + /// Writes the text representation of a 8-byte signed integer to the text stream, without allocating a string. + /// + /// The to write to. + /// The 8-byte unsigned integer to write. + /// A standard or custom numeric format string. + /// This method may still allocate if the integer is too large to fit in a stack-allocated buffer. + /// is . + /// The is closed. + /// An I/O error occurs. + [CLSCompliant(false)] + public static void WriteNoAlloc(this TextWriter writer, ulong value, ReadOnlySpan format) + { +#if NET6_0_OR_GREATER + ArgumentNullException.ThrowIfNull(writer); +#else + if (writer is null) + { + throw new ArgumentNullException(nameof(writer)); + } +#endif + + WriteNoAlloc(writer, value, format, CultureInfo.CurrentCulture); + } + + /// + /// Writes the text representation of a 8-byte signed integer to the text stream, without allocating a string. + /// + /// The to write to. + /// The 8-byte unsigned integer to write. + /// A standard or custom numeric format string. + /// An object that supplies culture-specific formatting information. + /// This method may still allocate if the integer is too large to fit in a stack-allocated buffer. + /// is . + /// The is closed. + /// An I/O error occurs. + [CLSCompliant(false)] + public static void WriteNoAlloc(this TextWriter writer, + ulong value, + ReadOnlySpan format, + IFormatProvider? formatProvider) + { +#if NET6_0_OR_GREATER + ArgumentNullException.ThrowIfNull(writer); +#else + if (writer is null) + { + throw new ArgumentNullException(nameof(writer)); + } +#endif + + int digitCount = value.CountDigits(); + Span buffer = stackalloc char[System.Math.Max(digitCount, 1000)]; + if (value.TryFormat(buffer, out int charsWritten, format, formatProvider)) + { + Span truncated = buffer[..charsWritten]; + for (var index = 0; index < truncated.Length; index++) + { + writer.Write(truncated[index]); + } + } + else + { + writer.Write(value.ToString(format.ToString(), formatProvider)); + } + } + + /// + /// Writes the text representation of a 4-byte signed integer to the text stream, followed by a line terminator, without + /// allocating a string. + /// + /// The to write to. + /// The 4-byte signed integer to write. + /// This method may still allocate if the integer is too large to fit in a stack-allocated buffer. + /// is . + /// The is closed. + /// An I/O error occurs. + public static void WriteLineNoAlloc(this TextWriter writer, int value) + { +#if NET6_0_OR_GREATER + ArgumentNullException.ThrowIfNull(writer); +#else + if (writer is null) + { + throw new ArgumentNullException(nameof(writer)); + } +#endif + + writer.WriteLineNoAlloc(value, "N0".AsSpan(), CultureInfo.CurrentCulture); + } + + /// + /// Writes the text representation of a 4-byte signed integer to the text stream, followed by a line terminator, without + /// allocating a string. + /// + /// The to write to. + /// The 4-byte signed integer to write. + /// A standard or custom numeric format string. + /// This method may still allocate if the integer is too large to fit in a stack-allocated buffer. + /// is . + /// The is closed. + /// An I/O error occurs. + public static void WriteLineNoAlloc(this TextWriter writer, int value, ReadOnlySpan format) + { +#if NET6_0_OR_GREATER + ArgumentNullException.ThrowIfNull(writer); +#else + if (writer is null) + { + throw new ArgumentNullException(nameof(writer)); + } +#endif + + writer.WriteLineNoAlloc(value, format, CultureInfo.CurrentCulture); + } + + /// + /// Writes the text representation of a 4-byte signed integer to the text stream, followed by a line terminator, without + /// allocating a string. + /// + /// The to write to. + /// The 4-byte signed integer to write. + /// A standard or custom numeric format string. + /// An object that supplies culture-specific formatting information. + /// This method may still allocate if the integer is too large to fit in a stack-allocated buffer. + /// is . + /// The is closed. + /// An I/O error occurs. + public static void WriteLineNoAlloc(this TextWriter writer, int value, ReadOnlySpan format, + IFormatProvider? formatProvider) + { +#if NET6_0_OR_GREATER + ArgumentNullException.ThrowIfNull(writer); +#else + if (writer is null) + { + throw new ArgumentNullException(nameof(writer)); + } +#endif + + writer.WriteNoAlloc(value, format, formatProvider); + writer.WriteLine(); + } + + /// + /// Writes the text representation of a 4-byte unsigned integer to the text stream, followed by a line terminator, without + /// allocating a string. + /// + /// The to write to. + /// The 4-byte unsigned integer to write. + /// This method may still allocate if the integer is too large to fit in a stack-allocated buffer. + /// is . + /// The is closed. + /// An I/O error occurs. + [CLSCompliant(false)] + public static void WriteLineNoAlloc(this TextWriter writer, uint value) + { +#if NET6_0_OR_GREATER + ArgumentNullException.ThrowIfNull(writer); +#else + if (writer is null) + { + throw new ArgumentNullException(nameof(writer)); + } +#endif + + writer.WriteLineNoAlloc(value, "N0".AsSpan(), CultureInfo.CurrentCulture); + } + + /// + /// Writes the text representation of a 4-byte unsigned integer to the text stream, followed by a line terminator, without + /// allocating a string. + /// + /// The to write to. + /// The 4-byte unsigned integer to write. + /// A standard or custom numeric format string. + /// This method may still allocate if the integer is too large to fit in a stack-allocated buffer. + /// is . + /// The is closed. + /// An I/O error occurs. + [CLSCompliant(false)] + public static void WriteLineNoAlloc(this TextWriter writer, uint value, ReadOnlySpan format) + { +#if NET6_0_OR_GREATER + ArgumentNullException.ThrowIfNull(writer); +#else + if (writer is null) + { + throw new ArgumentNullException(nameof(writer)); + } +#endif + + writer.WriteLineNoAlloc(value, format, CultureInfo.CurrentCulture); + } + + /// + /// Writes the text representation of a 4-byte unsigned integer to the text stream, followed by a line terminator, without + /// allocating a string. + /// + /// The to write to. + /// The 4-byte unsigned integer to write. + /// A standard or custom numeric format string. + /// An object that supplies culture-specific formatting information. + /// This method may still allocate if the integer is too large to fit in a stack-allocated buffer. + /// is . + /// The is closed. + /// An I/O error occurs. + [CLSCompliant(false)] + public static void WriteLineNoAlloc(this TextWriter writer, + uint value, + ReadOnlySpan format, + IFormatProvider? formatProvider) + { +#if NET6_0_OR_GREATER + ArgumentNullException.ThrowIfNull(writer); +#else + if (writer is null) + { + throw new ArgumentNullException(nameof(writer)); + } +#endif + + writer.WriteNoAlloc(value, format, formatProvider); + writer.WriteLine(); + } + + /// + /// Writes the text representation of a 8-byte signed integer to the text stream, followed by a line terminator, without + /// allocating a string. + /// + /// The to write to. + /// The 8-byte signed integer to write. + /// This method may still allocate if the integer is too large to fit in a stack-allocated buffer. + /// is . + /// The is closed. + /// An I/O error occurs. + public static void WriteLineNoAlloc(this TextWriter writer, long value) + { +#if NET6_0_OR_GREATER + ArgumentNullException.ThrowIfNull(writer); +#else + if (writer is null) + { + throw new ArgumentNullException(nameof(writer)); + } +#endif + + writer.WriteLineNoAlloc(value, "N0".AsSpan(), CultureInfo.CurrentCulture); + } + + /// + /// Writes the text representation of a 8-byte signed integer to the text stream, followed by a line terminator, without + /// allocating a string. + /// + /// The to write to. + /// The 8-byte signed integer to write. + /// A standard or custom numeric format string. + /// This method may still allocate if the integer is too large to fit in a stack-allocated buffer. + /// is . + /// The is closed. + /// An I/O error occurs. + public static void WriteLineNoAlloc(this TextWriter writer, long value, ReadOnlySpan format) + { +#if NET6_0_OR_GREATER + ArgumentNullException.ThrowIfNull(writer); +#else + if (writer is null) + { + throw new ArgumentNullException(nameof(writer)); + } +#endif + + writer.WriteLineNoAlloc(value, format, CultureInfo.CurrentCulture); + } + + /// + /// Writes the text representation of a 8-byte signed integer to the text stream, followed by a line terminator, without + /// allocating a string. + /// + /// The to write to. + /// The 8-byte signed integer to write. + /// A standard or custom numeric format string. + /// An object that supplies culture-specific formatting information. + /// This method may still allocate if the integer is too large to fit in a stack-allocated buffer. + /// is . + /// The is closed. + /// An I/O error occurs. + public static void WriteLineNoAlloc(this TextWriter writer, + long value, + ReadOnlySpan format, + IFormatProvider? formatProvider) + { +#if NET6_0_OR_GREATER + ArgumentNullException.ThrowIfNull(writer); +#else + if (writer is null) + { + throw new ArgumentNullException(nameof(writer)); + } +#endif + + writer.WriteNoAlloc(value, format, formatProvider); + writer.WriteLine(); + } + + /// + /// Writes the text representation of a 8-byte unsigned integer to the text stream, followed by a line terminator, without + /// allocating a string. + /// + /// The to write to. + /// The 8-byte unsigned integer to write. + /// This method may still allocate if the integer is too large to fit in a stack-allocated buffer. + /// is . + /// The is closed. + /// An I/O error occurs. + [CLSCompliant(false)] + public static void WriteLineNoAlloc(this TextWriter writer, ulong value) + { +#if NET6_0_OR_GREATER + ArgumentNullException.ThrowIfNull(writer); +#else + if (writer is null) + { + throw new ArgumentNullException(nameof(writer)); + } +#endif + + writer.WriteLineNoAlloc(value, "N0".AsSpan(), CultureInfo.CurrentCulture); + } + + /// + /// Writes the text representation of a 8-byte signed integer to the text stream, followed by a line terminator, without + /// allocating a string. + /// + /// The to write to. + /// The 8-byte unsigned integer to write. + /// A standard or custom numeric format string. + /// This method may still allocate if the integer is too large to fit in a stack-allocated buffer. + /// is . + /// The is closed. + /// An I/O error occurs. + [CLSCompliant(false)] + public static void WriteLineNoAlloc(this TextWriter writer, ulong value, ReadOnlySpan format) + { +#if NET6_0_OR_GREATER + ArgumentNullException.ThrowIfNull(writer); +#else + if (writer is null) + { + throw new ArgumentNullException(nameof(writer)); + } +#endif + + writer.WriteLineNoAlloc(value, format, CultureInfo.CurrentCulture); + } + + /// + /// Writes the text representation of a 8-byte signed integer to the text stream, followed by a line terminator, without + /// allocating a string. + /// + /// The to write to. + /// The 8-byte unsigned integer to write. + /// A standard or custom numeric format string. + /// An object that supplies culture-specific formatting information. + /// This method may still allocate if the integer is too large to fit in a stack-allocated buffer. + /// is . + /// The is closed. + /// An I/O error occurs. + [CLSCompliant(false)] + public static void WriteLineNoAlloc(this TextWriter writer, + ulong value, + ReadOnlySpan format, + IFormatProvider? formatProvider) + { +#if NET6_0_OR_GREATER + ArgumentNullException.ThrowIfNull(writer); +#else + if (writer is null) + { + throw new ArgumentNullException(nameof(writer)); + } +#endif + + writer.WriteNoAlloc(value, format, formatProvider); + writer.WriteLine(); + } +} From 57ff32ee94987bbf69bde049268a6d9a3c355378 Mon Sep 17 00:00:00 2001 From: Oliver Booth Date: Thu, 6 Apr 2023 19:18:20 +0100 Subject: [PATCH 8/9] [ci skip] docs: add comment explaining my absolute pain --- X10D.Tests/src/IO/TextWriterTests.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/X10D.Tests/src/IO/TextWriterTests.cs b/X10D.Tests/src/IO/TextWriterTests.cs index b98268a..750b352 100644 --- a/X10D.Tests/src/IO/TextWriterTests.cs +++ b/X10D.Tests/src/IO/TextWriterTests.cs @@ -15,6 +15,17 @@ public partial class TextWriterTests { _stream = new MemoryStream(); _writer = new StreamWriter(_stream, Encoding.UTF8); + + // When StreamWriter flushes for the first time, an encoding preamble is written to the stream, + // which is correctly mirrored by the behaviour of StreamReader. + // however, we're not using StreamReader, we read the contents of the stream + // using MemoryStream.ToArray(). This was causing one test to fail, as the first test + // that runs would cause the preamble to be written and not be accounted for when reading. + // Subsequent tests would pass since the preamble would not be written again. + // The following 4 lines ensure that the preamble is written by manually flushing the + // writer after writing a single space character. We then clear the stream, and allow + // unit tests to do their thing. This took me an HOUR AND A HALF to narrow down. + // I want to fucking die. _writer.Write(' '); _writer.Flush(); _stream.SetLength(0); From 75ac9e2d8f2905fde32c3216b65d521bd8b2cec9 Mon Sep 17 00:00:00 2001 From: Oliver Booth Date: Thu, 6 Apr 2023 19:22:27 +0100 Subject: [PATCH 9/9] chore: add MetaServices to sln --- X10D.sln | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/X10D.sln b/X10D.sln index 846b992..dd11b7d 100644 --- a/X10D.sln +++ b/X10D.sln @@ -47,6 +47,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tools", "Tools", "{4B8969E6 EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Benchmarks", "tools\Benchmarks\Benchmarks.csproj", "{259450A0-9964-403A-91E1-E9111B92C293}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "X10D.MetaServices", "tools\X10D.MetaServices\X10D.MetaServices.csproj", "{F57376C4-3591-43AF-BBED-447A1DE2B1FE}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -89,6 +91,10 @@ Global {259450A0-9964-403A-91E1-E9111B92C293}.Debug|Any CPU.Build.0 = Debug|Any CPU {259450A0-9964-403A-91E1-E9111B92C293}.Release|Any CPU.ActiveCfg = Release|Any CPU {259450A0-9964-403A-91E1-E9111B92C293}.Release|Any CPU.Build.0 = Release|Any CPU + {F57376C4-3591-43AF-BBED-447A1DE2B1FE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F57376C4-3591-43AF-BBED-447A1DE2B1FE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F57376C4-3591-43AF-BBED-447A1DE2B1FE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F57376C4-3591-43AF-BBED-447A1DE2B1FE}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -101,5 +107,6 @@ Global {CCBF047D-1B01-45EC-8D89-B00B4AC482CA} = {4B8969E6-27D2-4357-964E-9979FF7CC805} {259450A0-9964-403A-91E1-E9111B92C293} = {4B8969E6-27D2-4357-964E-9979FF7CC805} {077A5D33-AD55-4C55-8A67-972CEBC32C7A} = {4B8969E6-27D2-4357-964E-9979FF7CC805} + {F57376C4-3591-43AF-BBED-447A1DE2B1FE} = {4B8969E6-27D2-4357-964E-9979FF7CC805} EndGlobalSection EndGlobal