Skip to content

Commit

Permalink
EnumerateLines
Browse files Browse the repository at this point in the history
  • Loading branch information
SimonCropp committed Apr 1, 2024
1 parent cef9932 commit abb2dc9
Show file tree
Hide file tree
Showing 5 changed files with 157 additions and 1 deletion.
2 changes: 2 additions & 0 deletions api_list.include.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@
#### ReadOnlySpan<Char>

* `Boolean EndsWith(String, StringComparison)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.memoryextensions.endswith#system-memoryextensions-endswith-1(system-readonlyspan((-0))-system-readonlyspan((-0))))
* `SpanLineEnumerator EnumerateLines()` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.memoryextensions.enumeratelines#system-memoryextensions-enumeratelines(system-readonlyspan((system-char))))
* `Boolean SequenceEqual(String)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.memoryextensions.sequenceequal#system-memoryextensions-sequenceequal-1(system-readonlyspan((-0))-system-readonlyspan((-0))))
* `Boolean StartsWith(String, StringComparison)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.memoryextensions.startswith#system-memoryextensions-startswith-1(system-readonlyspan((-0))-system-readonlyspan((-0))))

Expand Down Expand Up @@ -226,6 +227,7 @@
#### Span<Char>

* `Boolean EndsWith(String)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.memoryextensions.endswith#system-memoryextensions-endswith-1(system-span((-0))-system-readonlyspan((-0))))
* `SpanLineEnumerator EnumerateLines()` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.memoryextensions.enumeratelines#system-memoryextensions-enumeratelines(system-span((system-char))))
* `Boolean SequenceEqual(String)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.memoryextensions.sequenceequal#system-memoryextensions-sequenceequal-1(system-span((-0))-system-readonlyspan((-0))))
* `Boolean StartsWith(String)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.memoryextensions.startswith#system-memoryextensions-startswith-1(system-span((-0))-system-readonlyspan((-0))))

Expand Down
7 changes: 7 additions & 0 deletions src/Consume/Consume.cs
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,13 @@ public void ListAddRangeReadOnlySpan()
list.AddRange("ab".AsSpan());
}

public void EnumerateLinesReadOnlySpan()
{
foreach (var line in "ab".AsSpan().EnumerateLines())
{
}
}

public void ListInsertRangeReadOnlySpan()
{
var list = new List<char>
Expand Down
40 changes: 39 additions & 1 deletion src/Polyfill/Polyfill_Memory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

#pragma warning disable

#if FeatureMemory && (NETFRAMEWORK || NETSTANDARD || NETCOREAPP2X)
#if FeatureMemory

using System;
using System.Collections.Generic;
Expand All @@ -12,6 +12,41 @@

static partial class Polyfill
{

#if !NET6_0_OR_GREATER

/// <summary>
/// Returns an enumeration of lines over the provided span.
/// </summary>
/// <remarks>
/// It is recommended that protocol parsers not utilize this API. See the documentation
/// for <see cref="string.ReplaceLineEndings"/> for more information on how newline
/// sequences are detected.
/// </remarks>
[Link("https://learn.microsoft.com/en-us/dotnet/api/system.memoryextensions.enumeratelines#system-memoryextensions-enumeratelines(system-readonlyspan((system-char)))")]
public static SpanLineEnumerator EnumerateLines(this ReadOnlySpan<char> span)
{
return new SpanLineEnumerator(span);
}

/// <summary>
/// Returns an enumeration of lines over the provided span.
/// </summary>
/// <remarks>
/// It is recommended that protocol parsers not utilize this API. See the documentation
/// for <see cref="string.ReplaceLineEndings"/> for more information on how newline
/// sequences are detected.
/// </remarks>
[Link("https://learn.microsoft.com/en-us/dotnet/api/system.memoryextensions.enumeratelines#system-memoryextensions-enumeratelines(system-span((system-char)))")]
public static SpanLineEnumerator EnumerateLines(this Span<char> span)
{
return new SpanLineEnumerator(span);
}

#endif

#if NETFRAMEWORK || NETSTANDARD || NETCOREAPP2X

/// <summary>
/// Indicates whether a specified value is found in a read-only span. Values are compared using IEquatable{T}.Equals(T).
/// </summary>
Expand Down Expand Up @@ -131,6 +166,9 @@ public static bool EndsWith(
this Span<char> target,
string other) =>
target.EndsWith(other.AsSpan());

#endif

}

#endif
92 changes: 92 additions & 0 deletions src/Polyfill/SpanLineEnumerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// <auto-generated />

#pragma warning disable

#if FeatureMemory && !NET6_0_OR_GREATER

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using Link = System.ComponentModel.DescriptionAttribute;

namespace System.Text;

/// <summary>
/// Enumerates the lines of a <see cref="ReadOnlySpan{Char}"/>.
/// </summary>
/// <remarks>
/// To get an instance of this type, use <see cref="MemoryExtensions.EnumerateLines(ReadOnlySpan{char})"/>.
/// </remarks>
[Link("https://learn.microsoft.com/en-us/dotnet/api/system.text.spanlineenumerator")]
public ref struct SpanLineEnumerator
{
private ReadOnlySpan<char> _remaining;
private ReadOnlySpan<char> _current;
private bool _isEnumeratorActive;
ReadOnlySpan<char> newlines = "\r\f\u0085\u2028\u2029\n".AsSpan();

internal SpanLineEnumerator(ReadOnlySpan<char> buffer)
{
_remaining = buffer;
_current = default;
_isEnumeratorActive = true;
}

/// <summary>
/// Gets the line at the current position of the enumerator.
/// </summary>
public ReadOnlySpan<char> Current => _current;

/// <summary>
/// Returns this instance as an enumerator.
/// </summary>
public SpanLineEnumerator GetEnumerator() => this;

/// <summary>
/// Advances the enumerator to the next line of the span.
/// </summary>
/// <returns>
/// True if the enumerator successfully advanced to the next line; false if
/// the enumerator has advanced past the end of the span.
/// </returns>
public bool MoveNext()
{
if (!_isEnumeratorActive)
{
// EOF previously reached or enumerator was never initialized
return false;
}

var remaining = _remaining;
//TODO: revisit when SearchValues is implemented
var idx = remaining.IndexOfAny(newlines);

if ((uint)idx < (uint)remaining.Length)
{
var stride = 1;

if (remaining[idx] == '\r' && (uint)(idx + 1) < (uint)remaining.Length && remaining[idx + 1] == '\n')
{
stride = 2;
}

_current = remaining[..idx];
_remaining = remaining[(idx + stride)..];
}
else
{
// We've reached EOF, but we still need to return 'true' for this final
// iteration so that the caller can query the Current property once more.

_current = remaining;
_remaining = default;
_isEnumeratorActive = false;
}

return true;
}
}
#endif
17 changes: 17 additions & 0 deletions src/Tests/PolyfillTests_Memory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,23 @@ public void ReadOnlySpan_ZeroLengthContains_String()
Assert.False(found);
}

[Test]
public void ReadOnlySpan_EnumerateLines()
{
var list = new List<string>();
var input = """
a
b
""";
foreach (var line in input.AsSpan().EnumerateLines())
{
list.Add(line.ToString());
}

Assert.Equals("a", list[0]);
Assert.Equals("b", list[1]);
}

[Test]
public void ReadOnlySpan_TestMatchContains_String()
{
Expand Down

0 comments on commit abb2dc9

Please sign in to comment.