From 195e25e0e3d7058e03b2ffab481fe671b88acbce Mon Sep 17 00:00:00 2001 From: Oliver Booth Date: Wed, 12 Jun 2024 03:40:34 +0100 Subject: [PATCH] feat: add range-based foreach --- CHANGELOG.md | 1 + X10D.Tests/src/Core/RangeTests.cs | 66 +++++++++++++++++++++++++++++++ X10D/src/Core/RangeEnumerator.cs | 53 +++++++++++++++++++++++++ X10D/src/Core/RangeExtensions.cs | 48 ++++++++++++++++++++++ 4 files changed, 168 insertions(+) create mode 100644 X10D.Tests/src/Core/RangeTests.cs create mode 100644 X10D/src/Core/RangeEnumerator.cs create mode 100644 X10D/src/Core/RangeExtensions.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 00ce823..6ebdd98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - X10D: Added `TextWriter.WriteLineNoAlloc(uint[, ReadOnlySpan[, IFormatProvider]])`. - X10D: Added `TextWriter.WriteLineNoAlloc(long[, ReadOnlySpan[, IFormatProvider]])`. - X10D: Added `TextWriter.WriteLineNoAlloc(ulong[, ReadOnlySpan[, 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. diff --git a/X10D.Tests/src/Core/RangeTests.cs b/X10D.Tests/src/Core/RangeTests.cs new file mode 100644 index 0000000..7acdbce --- /dev/null +++ b/X10D.Tests/src/Core/RangeTests.cs @@ -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()); + Assert.That((5..10).GetEnumerator(), Is.TypeOf()); + }); + } + + [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--; + } + } +} diff --git a/X10D/src/Core/RangeEnumerator.cs b/X10D/src/Core/RangeEnumerator.cs new file mode 100644 index 0000000..e2dd59b --- /dev/null +++ b/X10D/src/Core/RangeEnumerator.cs @@ -0,0 +1,53 @@ +namespace X10D.Core; + +/// +/// Enumerates the indices of a . +/// +public struct RangeEnumerator +{ + private readonly bool _decrement; + private readonly int _endValue; + + /// + /// Initializes a new instance of the structure. + /// + /// The range over which to enumerate. + 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; + } + + /// + /// Gets the element in the collection at the current position of the enumerator. + /// + /// The element in the collection at the current position of the enumerator. + public int Current { get; private set; } + + /// + /// Advances the enumerator to the next element of the collection. + /// + /// + /// if the enumerator was successfully advanced to the next element; if + /// the enumerator has passed the end of the collection. + /// + public bool MoveNext() + { + int value = Current; + + if (_decrement) + { + Current--; + return value > _endValue; + } + + Current++; + return value < _endValue; + } +} diff --git a/X10D/src/Core/RangeExtensions.cs b/X10D/src/Core/RangeExtensions.cs new file mode 100644 index 0000000..a6164bd --- /dev/null +++ b/X10D/src/Core/RangeExtensions.cs @@ -0,0 +1,48 @@ +namespace X10D.Core; + +/// +/// Extension methods for . +/// +public static class RangeExtensions +{ + /// + /// Allows the ability to use a for loop to iterate over the indices of a . The indices of the + /// range are the inclusive lower and upper bounds of the enumeration. + /// + /// The range whose indices over which will be enumerated. + /// A that will enumerate over the indices of . + /// + /// This method aims to implement Python-esque for loops in C# by taking advantage of the language syntax used to define + /// a value. Negative bounds may be specified using the C# ^ operator, or by providing an + /// whose property is . + /// + /// + /// The following example counts from 0 to 10 inclusive: + /// + /// foreach (var i in 0..10) + /// { + /// Console.WriteLine(i); + /// } + /// + /// + /// To use negative bounds, use the ^ operator. The following example counts from -5 to 5 inclusive: + /// + /// foreach (var i in ^5..5) + /// { + /// Console.WriteLine(i); + /// } + /// + /// + /// Decrementing enumeration is supported. The following example counts from 5 to -5 inclusive: + /// + /// foreach (var i in 5..^5) + /// { + /// Console.WriteLine(i); + /// } + /// + /// + public static RangeEnumerator GetEnumerator(this Range range) + { + return new RangeEnumerator(range); + } +}