diff --git a/src/libraries/System.Text.RegularExpressions/gen/RegexGenerator.Emitter.cs b/src/libraries/System.Text.RegularExpressions/gen/RegexGenerator.Emitter.cs
index 19488703f07f7e..23fb622ac705eb 100644
--- a/src/libraries/System.Text.RegularExpressions/gen/RegexGenerator.Emitter.cs
+++ b/src/libraries/System.Text.RegularExpressions/gen/RegexGenerator.Emitter.cs
@@ -735,11 +735,15 @@ private static void EmitTryFindNextPossibleStartingPosition(IndentedTextWriter w
case FindNextStartingPositionMode.LeadingString_LeftToRight:
case FindNextStartingPositionMode.LeadingString_OrdinalIgnoreCase_LeftToRight:
case FindNextStartingPositionMode.FixedDistanceString_LeftToRight:
- EmitIndexOf_LeftToRight();
+ EmitIndexOfString_LeftToRight();
break;
case FindNextStartingPositionMode.LeadingString_RightToLeft:
- EmitIndexOf_RightToLeft();
+ EmitIndexOfString_RightToLeft();
+ break;
+
+ case FindNextStartingPositionMode.LeadingStrings_LeftToRight:
+ EmitIndexOfStrings_LeftToRight();
break;
case FindNextStartingPositionMode.LeadingSet_LeftToRight:
@@ -964,7 +968,7 @@ bool EmitAnchors()
}
// Emits a case-sensitive left-to-right search for a substring.
- void EmitIndexOf_LeftToRight()
+ void EmitIndexOfString_LeftToRight()
{
RegexFindOptimizations opts = regexTree.FindOptimizations;
@@ -1010,8 +1014,43 @@ void EmitIndexOf_LeftToRight()
}
}
+ // Emits a case-sensitive left-to-right search for any one of multiple leading prefixes.
+ void EmitIndexOfStrings_LeftToRight()
+ {
+ RegexFindOptimizations opts = regexTree.FindOptimizations;
+ Debug.Assert(opts.FindMode == FindNextStartingPositionMode.LeadingStrings_LeftToRight);
+
+ string prefixes = string.Join(", ", opts.LeadingPrefixes.Select(prefix => Literal(prefix)));
+
+ string fieldName = "s_indexOfAnyStrings_";
+ using (SHA256 sha = SHA256.Create())
+ {
+#pragma warning disable CA1850 // SHA256.HashData isn't available on netstandard2.0
+ fieldName += $"{BitConverter.ToString(sha.ComputeHash(Encoding.UTF8.GetBytes(prefixes))).Replace("-", "")}";
+#pragma warning restore CA1850
+ }
+
+ if (!requiredHelpers.ContainsKey(fieldName))
+ {
+ requiredHelpers.Add(fieldName, new string[]
+ {
+ $"/// Supports searching for any of the strings {EscapeXmlComment(prefixes)}.",
+ $"internal static readonly SearchValues {fieldName} = SearchValues.Create(new[] {{ {prefixes} }}, StringComparison.Ordinal);",
+ });
+ }
+
+ writer.WriteLine($"// The pattern has multiple strings that could begin the match. Search for any of them.");
+ writer.WriteLine($"// If none can be found, there's no match.");
+ writer.WriteLine($"int i = inputSpan.Slice(pos).IndexOfAny({HelpersTypeName}.{fieldName});");
+ using (EmitBlock(writer, "if (i >= 0)"))
+ {
+ writer.WriteLine("base.runtextpos = pos + i;");
+ writer.WriteLine("return true;");
+ }
+ }
+
// Emits a case-sensitive right-to-left search for a substring.
- void EmitIndexOf_RightToLeft()
+ void EmitIndexOfString_RightToLeft()
{
string prefix = regexTree.FindOptimizations.LeadingPrefix;
diff --git a/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/CompiledRegexRunner.cs b/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/CompiledRegexRunner.cs
index b58f584662ed9a..827c6d5384b5d6 100644
--- a/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/CompiledRegexRunner.cs
+++ b/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/CompiledRegexRunner.cs
@@ -1,34 +1,35 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-using System.Buffers;
using System.Globalization;
+#pragma warning disable CA1823, CS0169, IDE0044 // Fields used via reflection
+
namespace System.Text.RegularExpressions
{
internal sealed class CompiledRegexRunner : RegexRunner
{
private readonly ScanDelegate _scanMethod;
- private readonly SearchValues[]? _searchValues;
+ /// Set if the regex uses any SearchValues instances. Accessed via reflection.
+ /// If the array is non-null, this contains instances of SearchValues{char} or SearchValues{string}.
+ private readonly object[]? _searchValues;
- /// This field will only be set if the pattern contains backreferences and has RegexOptions.IgnoreCase
+ /// Set if the pattern contains backreferences and has RegexOptions.IgnoreCase. Accessed via reflection.
private readonly CultureInfo? _culture;
-#pragma warning disable CA1823, CS0169, IDE0044 // Used via reflection to cache the Case behavior if needed.
+ /// Caches a RegexCaseBehavior. Accessed via reflection.
private RegexCaseBehavior _caseBehavior;
-#pragma warning restore CA1823, CS0169, IDE0044
internal delegate void ScanDelegate(RegexRunner runner, ReadOnlySpan text);
- public CompiledRegexRunner(ScanDelegate scan, SearchValues[]? searchValues, CultureInfo? culture)
+ public CompiledRegexRunner(ScanDelegate scan, object[]? searchValues, CultureInfo? culture)
{
_scanMethod = scan;
_searchValues = searchValues;
_culture = culture;
}
- protected internal override void Scan(ReadOnlySpan text)
- => _scanMethod(this, text);
+ protected internal override void Scan(ReadOnlySpan text) => _scanMethod(this, text);
}
}
diff --git a/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/CompiledRegexRunnerFactory.cs b/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/CompiledRegexRunnerFactory.cs
index 6784f068d1c559..83eb957327847e 100644
--- a/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/CompiledRegexRunnerFactory.cs
+++ b/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/CompiledRegexRunnerFactory.cs
@@ -1,7 +1,6 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-using System.Buffers;
using System.Globalization;
using System.Reflection.Emit;
@@ -10,14 +9,14 @@ namespace System.Text.RegularExpressions
internal sealed class CompiledRegexRunnerFactory : RegexRunnerFactory
{
private readonly DynamicMethod _scanMethod;
- private readonly SearchValues[]? _searchValues;
+ private readonly object[]? _searchValues;
/// This field will only be set if the pattern has backreferences and uses RegexOptions.IgnoreCase
private readonly CultureInfo? _culture;
// Delegate is lazily created to avoid forcing JIT'ing until the regex is actually executed.
private CompiledRegexRunner.ScanDelegate? _scan;
- public CompiledRegexRunnerFactory(DynamicMethod scanMethod, SearchValues[]? searchValues, CultureInfo? culture)
+ public CompiledRegexRunnerFactory(DynamicMethod scanMethod, object[]? searchValues, CultureInfo? culture)
{
_scanMethod = scanMethod;
_searchValues = searchValues;
diff --git a/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexCompiler.cs b/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexCompiler.cs
index 20ccc3afefcca6..8491cd831ed144 100644
--- a/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexCompiler.cs
+++ b/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexCompiler.cs
@@ -71,6 +71,7 @@ internal abstract class RegexCompiler
private static readonly MethodInfo s_spanIndexOfAnyCharCharChar = typeof(MemoryExtensions).GetMethod("IndexOfAny", new Type[] { typeof(ReadOnlySpan<>).MakeGenericType(Type.MakeGenericMethodParameter(0)), Type.MakeGenericMethodParameter(0), Type.MakeGenericMethodParameter(0), Type.MakeGenericMethodParameter(0) })!.MakeGenericMethod(typeof(char));
private static readonly MethodInfo s_spanIndexOfAnySpan = typeof(MemoryExtensions).GetMethod("IndexOfAny", new Type[] { typeof(ReadOnlySpan<>).MakeGenericType(Type.MakeGenericMethodParameter(0)), typeof(ReadOnlySpan<>).MakeGenericType(Type.MakeGenericMethodParameter(0)) })!.MakeGenericMethod(typeof(char));
private static readonly MethodInfo s_spanIndexOfAnySearchValues = typeof(MemoryExtensions).GetMethod("IndexOfAny", new Type[] { typeof(ReadOnlySpan<>).MakeGenericType(Type.MakeGenericMethodParameter(0)), typeof(SearchValues<>).MakeGenericType(Type.MakeGenericMethodParameter(0)) })!.MakeGenericMethod(typeof(char));
+ private static readonly MethodInfo s_spanIndexOfAnySearchValuesString = typeof(MemoryExtensions).GetMethod("IndexOfAny", new Type[] { typeof(ReadOnlySpan), typeof(SearchValues) })!;
private static readonly MethodInfo s_spanIndexOfAnyExceptChar = typeof(MemoryExtensions).GetMethod("IndexOfAnyExcept", new Type[] { typeof(ReadOnlySpan<>).MakeGenericType(Type.MakeGenericMethodParameter(0)), Type.MakeGenericMethodParameter(0) })!.MakeGenericMethod(typeof(char));
private static readonly MethodInfo s_spanIndexOfAnyExceptCharChar = typeof(MemoryExtensions).GetMethod("IndexOfAnyExcept", new Type[] { typeof(ReadOnlySpan<>).MakeGenericType(Type.MakeGenericMethodParameter(0)), Type.MakeGenericMethodParameter(0), Type.MakeGenericMethodParameter(0) })!.MakeGenericMethod(typeof(char));
private static readonly MethodInfo s_spanIndexOfAnyExceptCharCharChar = typeof(MemoryExtensions).GetMethod("IndexOfAnyExcept", new Type[] { typeof(ReadOnlySpan<>).MakeGenericType(Type.MakeGenericMethodParameter(0)), Type.MakeGenericMethodParameter(0), Type.MakeGenericMethodParameter(0), Type.MakeGenericMethodParameter(0) })!.MakeGenericMethod(typeof(char));
@@ -114,8 +115,8 @@ internal abstract class RegexCompiler
/// Whether this expression has a non-infinite timeout.
protected bool _hasTimeout;
- /// instances used by the expression. For now these are only ASCII sets.
- protected List>? _searchValues;
+ /// instances used by the expression.
+ protected List