diff --git a/CHANGELOG.md b/CHANGELOG.md index a0fcace..4de5308 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,15 @@ 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. +- 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..750b352 --- /dev/null +++ b/X10D.Tests/src/IO/TextWriterTests.cs @@ -0,0 +1,57 @@ +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); + + // 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); + _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.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.sln b/X10D.sln index 1d8368a..dd11b7d 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 @@ -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 @@ -100,5 +106,7 @@ 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} + {F57376C4-3591-43AF-BBED-447A1DE2B1FE} = {4B8969E6-27D2-4357-964E-9979FF7CC805} EndGlobalSection EndGlobal diff --git a/X10D/X10D.csproj b/X10D/X10D.csproj index 14eb676..e034d06 100644 --- a/X10D/X10D.csproj +++ b/X10D/X10D.csproj @@ -83,7 +83,8 @@ - + + 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(); + } +} 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. /// diff --git a/X10D.SourceGenerator/EmojiRegexGenerator.cs b/tools/SourceGenerator/EmojiRegexGenerator.cs similarity index 92% rename from X10D.SourceGenerator/EmojiRegexGenerator.cs rename to tools/SourceGenerator/EmojiRegexGenerator.cs index 2d67b1d..1ac9ca9 100644 --- a/X10D.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 @@ -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/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/X10D.SourceGenerator/X10D.SourceGenerator.csproj b/tools/SourceGenerator/SourceGenerator.csproj similarity index 85% rename from X10D.SourceGenerator/X10D.SourceGenerator.csproj rename to tools/SourceGenerator/SourceGenerator.csproj index e012f98..266d229 100644 --- a/X10D.SourceGenerator/X10D.SourceGenerator.csproj +++ b/tools/SourceGenerator/SourceGenerator.csproj @@ -17,4 +17,8 @@ + + + + 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 new file mode 100644 index 0000000..382e63e --- /dev/null +++ b/tools/X10D.MetaServices/AutoOverloadAttribute.cs @@ -0,0 +1,6 @@ +namespace X10D.MetaServices; + +[AttributeUsage(AttributeTargets.Method, Inherited = false)] +internal sealed class AutoOverloadAttribute : Attribute +{ +} diff --git a/tools/X10D.MetaServices/OverloadTypeAttribute.cs b/tools/X10D.MetaServices/OverloadTypeAttribute.cs new file mode 100644 index 0000000..0fdde1a --- /dev/null +++ b/tools/X10D.MetaServices/OverloadTypeAttribute.cs @@ -0,0 +1,20 @@ +namespace X10D.MetaServices; + +[AttributeUsage(AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.ReturnValue, Inherited = false)] +internal 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; } +} 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 + + +