feat: add range-based foreach

This commit is contained in:
Oliver Booth 2024-06-12 03:40:34 +01:00
parent bd1b12da71
commit 195e25e0e3
Signed by: oliverbooth
GPG Key ID: E60B570D1B7557B5
4 changed files with 168 additions and 0 deletions

View File

@ -23,6 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- X10D: Added `TextWriter.WriteLineNoAlloc(uint[, ReadOnlySpan<char>[, IFormatProvider]])`.
- X10D: Added `TextWriter.WriteLineNoAlloc(long[, ReadOnlySpan<char>[, IFormatProvider]])`.
- X10D: Added `TextWriter.WriteLineNoAlloc(ulong[, ReadOnlySpan<char>[, IFormatProvider]])`.
- X10D: Added `Range.GetEnumerator` (and `RangeEnumerator`), implementing Python-esque `for` loops in C#.
- X10D: Added `string.ConcatIf`.
- X10D: Added `string.MDBold`, `string.MDCode`, `string.MDCodeBlock([string])`, `string.MDHeading(int)`,
`string.MDItalic`, `string.MDLink`, `string.MDStrikeOut`, and `string.MDUnderline` for Markdown formatting.

View File

@ -0,0 +1,66 @@
using NUnit.Framework;
using X10D.Core;
namespace X10D.Tests.Core;
[TestFixture]
internal class RangeTests
{
[Test]
public void Range_GetEnumerator_ShouldReturnRangeEnumerator()
{
Assert.Multiple(() =>
{
Assert.That(5..10, Is.TypeOf<Range>());
Assert.That((5..10).GetEnumerator(), Is.TypeOf<RangeEnumerator>());
});
}
[Test]
public void Loop_OverRange0To10_ShouldCountFrom0To10Inclusive()
{
int value = 0;
foreach (int i in 0..10)
{
Assert.That(i, Is.EqualTo(value));
value++;
}
}
[Test]
public void Loop_OverRangeNegative5To5_ShouldCountFromNegative5To5Inclusive()
{
int value = -5;
foreach (int i in ^5..5)
{
Assert.That(i, Is.EqualTo(value));
value++;
}
}
[Test]
public void Loop_OverRange5ToNegative5_ShouldCountFrom5ToNegative5Inclusive()
{
int value = 5;
foreach (int i in 5..^5)
{
Assert.That(i, Is.EqualTo(value));
value--;
}
}
[Test]
public void Loop_OverRange10To0_ShouldCountFrom10To0Inclusive()
{
int value = 10;
foreach (int i in 10..0)
{
Assert.That(i, Is.EqualTo(value));
value--;
}
}
}

View File

@ -0,0 +1,53 @@
namespace X10D.Core;
/// <summary>
/// Enumerates the indices of a <see cref="Range" />.
/// </summary>
public struct RangeEnumerator
{
private readonly bool _decrement;
private readonly int _endValue;
/// <summary>
/// Initializes a new instance of the <see cref="RangeEnumerator" /> structure.
/// </summary>
/// <param name="range">The range over which to enumerate.</param>
public RangeEnumerator(Range range)
{
Index start = range.Start;
Index end = range.End;
int startValue = start.IsFromEnd ? -start.Value : start.Value;
_endValue = end.IsFromEnd ? -end.Value : end.Value;
_decrement = _endValue < startValue;
Current = _decrement ? startValue + 1 : startValue - 1;
}
/// <summary>
/// Gets the element in the collection at the current position of the enumerator.
/// </summary>
/// <value>The element in the collection at the current position of the enumerator.</value>
public int Current { get; private set; }
/// <summary>
/// Advances the enumerator to the next element of the collection.
/// </summary>
/// <returns>
/// <see langword="true" /> if the enumerator was successfully advanced to the next element; <see langword="false" /> if
/// the enumerator has passed the end of the collection.
/// </returns>
public bool MoveNext()
{
int value = Current;
if (_decrement)
{
Current--;
return value > _endValue;
}
Current++;
return value < _endValue;
}
}

View File

@ -0,0 +1,48 @@
namespace X10D.Core;
/// <summary>
/// Extension methods for <see cref="Range" />.
/// </summary>
public static class RangeExtensions
{
/// <summary>
/// Allows the ability to use a <c>for</c> loop to iterate over the indices of a <see cref="Range" />. The indices of the
/// range are the inclusive lower and upper bounds of the enumeration.
/// </summary>
/// <param name="range">The range whose indices over which will be enumerated.</param>
/// <returns>A <see cref="RangeEnumerator" /> that will enumerate over the indices of <paramref name="range" />.</returns>
/// <remarks>
/// This method aims to implement Python-esque for loops in C# by taking advantage of the language syntax used to define
/// a <see cref="Range" /> value. Negative bounds may be specified using the C# <c>^</c> operator, or by providing an
/// <see cref="Index" /> whose <see cref="Index.IsFromEnd" /> property is <see langword="true" />.
/// </remarks>
/// <example>
/// The following example counts from 0 to 10 inclusive:
/// <code>
/// foreach (var i in 0..10)
/// {
/// Console.WriteLine(i);
/// }
/// </code>
///
/// To use negative bounds, use the <c>^</c> operator. The following example counts from -5 to 5 inclusive:
/// <code>
/// foreach (var i in ^5..5)
/// {
/// Console.WriteLine(i);
/// }
/// </code>
///
/// Decrementing enumeration is supported. The following example counts from 5 to -5 inclusive:
/// <code>
/// foreach (var i in 5..^5)
/// {
/// Console.WriteLine(i);
/// }
/// </code>
/// </example>
public static RangeEnumerator GetEnumerator(this Range range)
{
return new RangeEnumerator(range);
}
}