Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Regex.EnumerateMatches #134

Merged
merged 8 commits into from
Feb 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions api_list.include.md
Original file line number Diff line number Diff line change
Expand Up @@ -214,8 +214,10 @@
* `Boolean TryCopyTo(Span<Char>)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.string.trycopyto)


#### RegularExpressions.Regex
#### Regex

* `ValueMatchEnumerator EnumerateMatches(ReadOnlySpan<Char>)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.text.regularexpressions.regex.enumeratematches#system-text-regularexpressions-regex-enumeratematches(system-readonlyspan((system-char))))
* `ValueMatchEnumerator EnumerateMatches(ReadOnlySpan<Char>, Int32)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.text.regularexpressions.regex.enumeratematches#system-text-regularexpressions-regex-enumeratematches(system-readonlyspan((system-char))-system-int32))
* `Boolean IsMatch(ReadOnlySpan<Char>, Int32)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.text.regularexpressions.regex.ismatch#system-text-regularexpressions-regex-ismatch(system-readonlyspan((system-char))-system-int32))
* `Boolean IsMatch(ReadOnlySpan<Char>)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.text.regularexpressions.regex.ismatch#system-text-regularexpressions-regex-ismatch(system-readonlyspan((system-char))))

Expand Down Expand Up @@ -300,8 +302,8 @@

#### RegexPolyfill

* `Boolean IsMatch(String, RegularExpressions.RegexOptions, TimeSpan)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.text.regularexpressions.regex.ismatch#system-text-regularexpressions-regex-ismatch(system-readonlyspan((system-char))-system-string-system-text-regularexpressions-regexoptions-system-timespan))
* `Boolean IsMatch(String, RegularExpressions.RegexOptions)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.text.regularexpressions.regex.ismatch#system-text-regularexpressions-regex-ismatch(system-readonlyspan((system-char))-system-string-system-text-regularexpressions-regexoptions))
* `Boolean IsMatch(String, RegexOptions, TimeSpan)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.text.regularexpressions.regex.ismatch#system-text-regularexpressions-regex-ismatch(system-readonlyspan((system-char))-system-string-system-text-regularexpressions-regexoptions-system-timespan))
* `Boolean IsMatch(String, RegexOptions)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.text.regularexpressions.regex.ismatch#system-text-regularexpressions-regex-ismatch(system-readonlyspan((system-char))-system-string-system-text-regularexpressions-regexoptions))
* `Boolean IsMatch(String)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.text.regularexpressions.regex.ismatch#system-text-regularexpressions-regex-ismatch(system-readonlyspan((system-char))-system-string))


8 changes: 5 additions & 3 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -570,8 +570,10 @@ The class `Polyfill` includes the following extension methods:
* `Boolean TryCopyTo(Span<Char>)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.string.trycopyto)


#### RegularExpressions.Regex
#### Regex

* `ValueMatchEnumerator EnumerateMatches(ReadOnlySpan<Char>)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.text.regularexpressions.regex.enumeratematches#system-text-regularexpressions-regex-enumeratematches(system-readonlyspan((system-char))))
* `ValueMatchEnumerator EnumerateMatches(ReadOnlySpan<Char>, Int32)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.text.regularexpressions.regex.enumeratematches#system-text-regularexpressions-regex-enumeratematches(system-readonlyspan((system-char))-system-int32))
* `Boolean IsMatch(ReadOnlySpan<Char>, Int32)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.text.regularexpressions.regex.ismatch#system-text-regularexpressions-regex-ismatch(system-readonlyspan((system-char))-system-int32))
* `Boolean IsMatch(ReadOnlySpan<Char>)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.text.regularexpressions.regex.ismatch#system-text-regularexpressions-regex-ismatch(system-readonlyspan((system-char))))

Expand Down Expand Up @@ -656,8 +658,8 @@ The class `Polyfill` includes the following extension methods:

#### RegexPolyfill

* `Boolean IsMatch(String, RegularExpressions.RegexOptions, TimeSpan)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.text.regularexpressions.regex.ismatch#system-text-regularexpressions-regex-ismatch(system-readonlyspan((system-char))-system-string-system-text-regularexpressions-regexoptions-system-timespan))
* `Boolean IsMatch(String, RegularExpressions.RegexOptions)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.text.regularexpressions.regex.ismatch#system-text-regularexpressions-regex-ismatch(system-readonlyspan((system-char))-system-string-system-text-regularexpressions-regexoptions))
* `Boolean IsMatch(String, RegexOptions, TimeSpan)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.text.regularexpressions.regex.ismatch#system-text-regularexpressions-regex-ismatch(system-readonlyspan((system-char))-system-string-system-text-regularexpressions-regexoptions-system-timespan))
* `Boolean IsMatch(String, RegexOptions)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.text.regularexpressions.regex.ismatch#system-text-regularexpressions-regex-ismatch(system-readonlyspan((system-char))-system-string-system-text-regularexpressions-regexoptions))
* `Boolean IsMatch(String)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.text.regularexpressions.regex.ismatch#system-text-regularexpressions-regex-ismatch(system-readonlyspan((system-char))-system-string))

<!-- endInclude -->
Expand Down
46 changes: 41 additions & 5 deletions src/Polyfill/Regex/Polyfill_Regex.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,10 @@

#pragma warning disable

#if (MEMORYREFERENCED && !NET7_0_OR_GREATER)
#if !NET7_0_OR_GREATER && HAS_SPAN

using System;
using System.IO;
using System.Net.Http;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Link = System.ComponentModel.DescriptionAttribute;

static partial class Polyfill
Expand All @@ -33,5 +29,45 @@ public static bool IsMatch(this Regex target, ReadOnlySpan<char> input)
{
return target.IsMatch(input.ToString());
}

/// <summary>
/// Searches an input span for all occurrences of a regular expression and returns a Regex.ValueMatchEnumerator to iterate over the matches.
/// </summary>
/// <returns>A Regex.ValueMatchEnumerator to iterate over the matches.</returns>
[Link("https://learn.microsoft.com/en-us/dotnet/api/system.text.regularexpressions.regex.enumeratematches#system-text-regularexpressions-regex-enumeratematches(system-readonlyspan((system-char)))")]
public static ValueMatchEnumerator EnumerateMatches (this Regex target, ReadOnlySpan<char> input) =>
new(target, input, target.RightToLeft ? input.Length : 0);

/// <summary>
/// Searches an input span for all occurrences of a regular expression and returns a Regex.ValueMatchEnumerator to iterate over the matches.
/// </summary>
/// <returns>A Regex.ValueMatchEnumerator to iterate over the matches.</returns>
[Link("https://learn.microsoft.com/en-us/dotnet/api/system.text.regularexpressions.regex.enumeratematches#system-text-regularexpressions-regex-enumeratematches(system-readonlyspan((system-char))-system-int32)")]
public static ValueMatchEnumerator EnumerateMatches (this Regex target, ReadOnlySpan<char> input, int startat) =>
new(target, input, startat);
//
// /// <summary>
// /// Searches an input span for all occurrences of a regular expression and returns a Regex.ValueMatchEnumerator to iterate over the matches.
// /// </summary>
// /// <returns>A Regex.ValueMatchEnumerator to iterate over the matches.</returns>
// [Link("https://learn.microsoft.com/en-us/dotnet/api/system.text.regularexpressions.regex.enumeratematches#system-text-regularexpressions-regex-enumeratematches(system-readonlyspan((system-char))-system-string)")]
// public static ValueMatchEnumerator EnumerateMatches (this Regex target, ReadOnlySpan<char> input, string pattern) =>
// new(target, input, target.RightToLeft ? input.Length : 0);
//
// /// <summary>
// /// Searches an input span for all occurrences of a regular expression and returns a Regex.ValueMatchEnumerator to iterate over the matches.
// /// </summary>
// /// <returns>A Regex.ValueMatchEnumerator to iterate over the matches.</returns>
// [Link("https://learn.microsoft.com/en-us/dotnet/api/system.text.regularexpressions.regex.enumeratematches#system-text-regularexpressions-regex-enumeratematches(system-readonlyspan((system-char))-system-string-system-text-regularexpressions-regexoptions)")]
// public static ValueMatchEnumerator EnumerateMatches (this Regex target, ReadOnlySpan<char> input, string pattern, System.Text.RegularExpressions.RegexOptions options) =>
// new(target, input, target.RightToLeft ? input.Length : 0);
//
// /// <summary>
// /// Searches an input span for all occurrences of a regular expression and returns a Regex.ValueMatchEnumerator to iterate over the matches.
// /// </summary>
// /// <returns>A Regex.ValueMatchEnumerator to iterate over the matches.</returns>
// [Link("https://learn.microsoft.com/en-us/dotnet/api/system.text.regularexpressions.regex.enumeratematches#system-text-regularexpressions-regex-enumeratematches(system-readonlyspan((system-char))-system-string-system-text-regularexpressions-regexoptions-system-timespan)")]
// public static ValueMatchEnumerator EnumerateMatches (this Regex target, ReadOnlySpan<char> input, string pattern, System.Text.RegularExpressions.RegexOptions options, TimeSpan matchTimeout) =>
// new(target, input, target.RightToLeft ? input.Length : 0);
}
#endif
5 changes: 2 additions & 3 deletions src/Polyfill/Regex/RegexPolyfill.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
// <auto-generated />

#pragma warning disable
#if MEMORYREFERENCED
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Text.RegularExpressions;
using Link = System.ComponentModel.DescriptionAttribute;
Expand All @@ -14,6 +12,7 @@
#endif
static partial class RegexPolyfill
{
#if HAS_SPAN
/// <summary>
/// Indicates whether the specified regular expression finds a match in the specified input span, using the specified matching options and time-out interval.
/// </summary>
Expand Down Expand Up @@ -55,5 +54,5 @@ public static bool IsMatch(ReadOnlySpan<char> input, string pattern)
return Regex.IsMatch(input.ToString(), pattern);
#endif
}
}
#endif
}
50 changes: 50 additions & 0 deletions src/Polyfill/Regex/ValueMatch.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// <auto-generated />

#pragma warning disable

#if !NET7_0_OR_GREATER && HAS_SPAN

using System;
using System.Diagnostics.CodeAnalysis;

namespace System.Text.RegularExpressions;

/// <summary>
/// Represents the results from a single regular expression match.
/// </summary>
/// <remarks>
/// The <see cref="ValueMatch"/> type is immutable and has no public constructor. An instance of the <see cref="ValueMatch"/> struct is returned by the
/// <see cref="Regex.ValueMatchEnumerator.Current"/> method when iterating over the results from calling <see cref="Regex.EnumerateMatches(ReadOnlySpan{char})"/>.
/// </remarks>
[ExcludeFromCodeCoverage]
#if PolyPublic
public
#endif
readonly ref struct ValueMatch
{
private readonly int _index;
private readonly int _length;

/// <summary>
/// Crates an instance of the <see cref="ValueMatch"/> type based on the passed in <paramref name="index"/> and <paramref name="length"/>.
/// </summary>
/// <param name="index">The position in the original span where the first character of the captured sliced span is found.</param>
/// <param name="length">The length of the captured sliced span.</param>
internal ValueMatch(int index, int length)
{
_index = index;
_length = length;
}

/// <summary>
/// Gets the position in the original span where the first character of the captured sliced span is found.
/// </summary>
public int Index => _index;

/// <summary>
/// Gets the length of the captured sliced span.
/// </summary>
public int Length => _length;
}

#endif
79 changes: 79 additions & 0 deletions src/Polyfill/Regex/ValueMatchEnumerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// <auto-generated />

#pragma warning disable

#if !NET7_0_OR_GREATER && HAS_SPAN

using System;
using System.Diagnostics.CodeAnalysis;

namespace System.Text.RegularExpressions;

/// <summary>
/// Represents an enumerator containing the set of successful matches found by iteratively applying a regular expression pattern to the input span.
/// </summary>
/// <remarks>
/// The enumerator has no public constructor. The <see cref="Regex.EnumerateMatches(ReadOnlySpan{char})"/> method returns a <see cref="Regex.ValueMatchEnumerator"/>
/// object.The enumerator will lazily iterate over zero or more <see cref="ValueMatch"/> objects. If there is at least one successful match in the span, then
/// <see cref="MoveNext"/> returns <see langword="true"/> and <see cref="Current"/> will contain the first <see cref="ValueMatch"/>. If there are no successful matches,
/// then <see cref="MoveNext"/> returns <see langword="false"/> and <see cref="Current"/> throws an <see cref="InvalidOperationException"/>.
///
/// This type is a ref struct since it stores the input span as a field in order to be able to lazily iterate over it.
/// </remarks>
[ExcludeFromCodeCoverage]
#if PolyPublic
public
#endif
ref struct ValueMatchEnumerator
{
ReadOnlySpan<char> _input;
ValueMatch _current;
MatchCollection matchCollection;
int index = 0;

/// <summary>
/// Creates an instance of the <see cref="ValueMatchEnumerator"/> for the passed in <paramref name="regex"/> which iterates over <paramref name="input"/>.
/// </summary>
/// <param name="regex">The <see cref="Regex"/> to use for finding matches.</param>
/// <param name="input">The input span to iterate over.</param>
/// <param name="startAt">The position where the engine should start looking for matches from.</param>
internal ValueMatchEnumerator(Regex regex, ReadOnlySpan<char> input, int startAt)
{
matchCollection = regex.Matches(input.ToString(), startAt);
_input = input;
_current = default;
}

/// <summary>
/// Provides an enumerator that iterates through the matches in the input span.
/// </summary>
/// <returns>A copy of this enumerator.</returns>
public readonly ValueMatchEnumerator GetEnumerator() => this;

/// <summary>
/// Advances the enumerator to the next match in the span.
/// </summary>
/// <returns>
/// <see langword="true"/> if the enumerator was successfully advanced to the next element; <see langword="false"/> if the enumerator cannot find additional matches.
/// </returns>
public bool MoveNext()
{
if (index == matchCollection.Count)
{
return false;
}

var match = matchCollection[index];
_current = new ValueMatch(match.Index, match.Length);
index++;
return true;
}

/// <summary>
/// Gets the <see cref="ValueMatch"/> element at the current position of the enumerator.
/// </summary>
/// <exception cref="InvalidOperationException">Enumeration has either not started or has already finished.</exception>
public readonly ValueMatch Current => _current;
}

#endif
3 changes: 2 additions & 1 deletion src/Tests/BuildApiTest.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
#if NET8_0 && DEBUG
using System.Diagnostics.CodeAnalysis;
using Mono.Cecil;

#if NET8_0 && DEBUG
[TestFixture]
class BuildApiTest
{
static string[] namespacesToClean =
[
"System.Text.RegularExpressions.",
"System.Diagnostics.",
"System.Collections.Generic.",
"System.Threading.Tasks.",
Expand Down
28 changes: 28 additions & 0 deletions src/Tests/PolyfillTests_Regex.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using System.Text.RegularExpressions;

partial class PolyfillTests
{
[Test]
public void RegexIsMatch()
{
var regex = new Regex(@"\d+");
var match = regex.Match("a55a");
Assert.IsTrue(match.Success);
}

[Test]
public void EnumerateMatches()
{
var regex = new Regex(@"\d+");
var span = "a55a".AsSpan();
var found = false;
foreach (var match in regex.EnumerateMatches(span))
{
found = true;
Assert.AreEqual(1, match.Index);
Assert.AreEqual(2, match.Length);
}

Assert.IsTrue(found);
}
}
Loading