diff --git a/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexParser.cs b/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexParser.cs index 275f3f0c08b96..7216562d29ac9 100644 --- a/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexParser.cs +++ b/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexParser.cs @@ -1,6 +1,7 @@ // 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.Collections; using System.Collections.Generic; using System.Diagnostics; @@ -149,18 +150,13 @@ public static RegexReplacement ParseReplacement(string pattern, RegexOptions opt /// public static string Escape(string input) { - for (int i = 0; i < input.Length; i++) - { - if (IsMetachar(input[i])) - { - return EscapeImpl(input, i); - } - } - - return input; + int indexOfMetachar = IndexOfMetachar(input.AsSpan()); + return indexOfMetachar < 0 + ? input + : EscapeImpl(input.AsSpan(), indexOfMetachar); } - private static string EscapeImpl(string input, int i) + private static string EscapeImpl(ReadOnlySpan input, int indexOfMetachar) { // For small inputs we allocate on the stack. In most cases a buffer three // times larger the original string should be sufficient as usually not all @@ -171,12 +167,18 @@ private static string EscapeImpl(string input, int i) new ValueStringBuilder(stackalloc char[EscapeMaxBufferSize]) : new ValueStringBuilder(input.Length + 200); - char ch = input[i]; - vsb.Append(input.AsSpan(0, i)); - - do + while (true) { - vsb.Append('\\'); + vsb.Append(input.Slice(0, indexOfMetachar)); + input = input.Slice(indexOfMetachar); + + if (input.IsEmpty) + { + break; + } + + char ch = input[0]; + switch (ch) { case '\n': @@ -193,23 +195,16 @@ private static string EscapeImpl(string input, int i) break; } + vsb.Append('\\'); vsb.Append(ch); - i++; - int lastpos = i; + input = input.Slice(1); - while (i < input.Length) + indexOfMetachar = IndexOfMetachar(input); + if (indexOfMetachar < 0) { - ch = input[i]; - if (IsMetachar(ch)) - { - break; - } - - i++; + indexOfMetachar = input.Length; } - - vsb.Append(input.AsSpan(lastpos, i - lastpos)); - } while (i < input.Length); + } return vsb.ToString(); } @@ -2081,6 +2076,27 @@ internal static int MapCaptureNumber(int capnum, Hashtable? caps) => // ' a b c d e f g h i j k l m n o p q r s t u v w x y z { | } ~ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, Q, S, 0, 0, 0}; +#if NET7_0_OR_GREATER + private static readonly IndexOfAnyValues s_metachars = + IndexOfAnyValues.Create("\t\n\f\r #$()*+.?[\\^{|"); + + private static int IndexOfMetachar(ReadOnlySpan input) => + input.IndexOfAny(s_metachars); +#else + private static int IndexOfMetachar(ReadOnlySpan input) + { + for (int i = 0; i < input.Length; i++) + { + if (IsMetachar(input[i])) + { + return i; + } + } + + return -1; + } +#endif + /// Returns true for those characters that terminate a string of ordinary chars. private static bool IsSpecial(char ch) => ch <= '|' && Category[ch] >= S;