Imported Upstream version 5.16.0.100

Former-commit-id: 38faa55fb9669e35e7d8448b15c25dc447f25767
This commit is contained in:
Xamarin Public Jenkins (auto-signing)
2018-08-07 15:19:03 +00:00
parent 0a9828183b
commit 7d7f676260
4419 changed files with 170950 additions and 90273 deletions

View File

@@ -7,6 +7,11 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Text.RegularExpressi
{2C58640B-5BED-4E83-9554-CD2B9762643F} = {2C58640B-5BED-4E83-9554-CD2B9762643F}
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Text.RegularExpressions.Performance.Tests", "tests\Performance\System.Text.RegularExpressions.Performance.Tests.csproj", "{7f4b8c48-8692-4885-bf84-feb7ea82e34b}"
ProjectSection(ProjectDependencies) = postProject
{2C58640B-5BED-4E83-9554-CD2B9762643F} = {2C58640B-5BED-4E83-9554-CD2B9762643F}
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Text.RegularExpressions", "src\System.Text.RegularExpressions.csproj", "{2C58640B-5BED-4E83-9554-CD2B9762643F}"
ProjectSection(ProjectDependencies) = postProject
{B262B15E-13E6-4C1E-A25E-16D06E222A09} = {B262B15E-13E6-4C1E-A25E-16D06E222A09}
@@ -30,6 +35,10 @@ Global
{94B106C2-D574-4392-80AB-3EE308A078DF}.Debug|Any CPU.Build.0 = netcoreapp-Debug|Any CPU
{94B106C2-D574-4392-80AB-3EE308A078DF}.Release|Any CPU.ActiveCfg = netcoreapp-Release|Any CPU
{94B106C2-D574-4392-80AB-3EE308A078DF}.Release|Any CPU.Build.0 = netcoreapp-Release|Any CPU
{7f4b8c48-8692-4885-bf84-feb7ea82e34b}.Debug|Any CPU.ActiveCfg = netcoreapp-Debug|Any CPU
{7f4b8c48-8692-4885-bf84-feb7ea82e34b}.Debug|Any CPU.Build.0 = netcoreapp-Debug|Any CPU
{7f4b8c48-8692-4885-bf84-feb7ea82e34b}.Release|Any CPU.ActiveCfg = netcoreapp-Release|Any CPU
{7f4b8c48-8692-4885-bf84-feb7ea82e34b}.Release|Any CPU.Build.0 = netcoreapp-Release|Any CPU
{2C58640B-5BED-4E83-9554-CD2B9762643F}.Debug|Any CPU.ActiveCfg = netcoreapp-Debug|Any CPU
{2C58640B-5BED-4E83-9554-CD2B9762643F}.Debug|Any CPU.Build.0 = netcoreapp-Debug|Any CPU
{2C58640B-5BED-4E83-9554-CD2B9762643F}.Release|Any CPU.ActiveCfg = netcoreapp-Release|Any CPU
@@ -44,6 +53,7 @@ Global
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{94B106C2-D574-4392-80AB-3EE308A078DF} = {1A2F9F4A-A032-433E-B914-ADD5992BB178}
{7f4b8c48-8692-4885-bf84-feb7ea82e34b} = {1A2F9F4A-A032-433E-B914-ADD5992BB178}
{2C58640B-5BED-4E83-9554-CD2B9762643F} = {E107E9C1-E893-4E87-987E-04EF0DCEAEFD}
{B262B15E-13E6-4C1E-A25E-16D06E222A09} = {2E666815-2EDB-464B-9DF6-380BF4789AD4}
EndGlobalSection

View File

@@ -0,0 +1,2 @@
# API is only for Debug purposes
MembersMustExist : Member 'System.Text.RegularExpressions.RegexOptions System.Text.RegularExpressions.RegexOptions.Debug' does not exist in the implementation but it does exist in the contract.

View File

@@ -5,42 +5,54 @@
<ProjectGuid>{2C58640B-5BED-4E83-9554-CD2B9762643F}</ProjectGuid>
<AssemblyName>System.Text.RegularExpressions</AssemblyName>
<DefineConstants Condition="'$(TargetGroup)' == 'netcoreapp'">$(DefineConstants);FEATURE_COMPILED</DefineConstants>
<ILLinkClearInitLocals>true</ILLinkClearInitLocals>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'netcoreapp-Debug|AnyCPU'" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'netcoreapp-Release|AnyCPU'" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'uap-Windows_NT-Debug|AnyCPU'" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'uap-Windows_NT-Release|AnyCPU'" />
<ItemGroup>
<Compile Include="System\Text\RegularExpressions\HashtableExtensions.cs" />
<Compile Include="System\Collections\Generic\ValueListBuilder.Pop.cs" />
<Compile Include="System\Collections\HashtableExtensions.cs" />
<Compile Include="System\Text\RegularExpressions\Capture.cs" />
<Compile Include="System\Text\RegularExpressions\CaptureCollection.cs" />
<Compile Include="System\Text\RegularExpressions\CollectionDebuggerProxy.cs" />
<Compile Include="System\Text\RegularExpressions\Group.cs" />
<Compile Include="System\Text\RegularExpressions\GroupCollection.cs" />
<Compile Include="System\Text\RegularExpressions\Match.cs" />
<Compile Include="System\Text\RegularExpressions\MatchCollection.cs" />
<Compile Include="System\Text\RegularExpressions\Reference.cs" />
<Compile Include="System\Text\RegularExpressions\Regex.Cache.cs" />
<Compile Include="System\Text\RegularExpressions\Regex.cs" />
<Compile Include="System\Text\RegularExpressions\Regex.Match.cs" />
<Compile Include="System\Text\RegularExpressions\Regex.Replace.cs" />
<Compile Include="System\Text\RegularExpressions\Regex.Split.cs" />
<Compile Include="System\Text\RegularExpressions\Regex.Timeout.cs" />
<Compile Include="System\Text\RegularExpressions\RegexBoyerMoore.cs" />
<Compile Include="System\Text\RegularExpressions\RegexCapture.cs" />
<Compile Include="System\Text\RegularExpressions\RegexCaptureCollection.cs" />
<Compile Include="System\Text\RegularExpressions\RegexCharClass.cs" />
<Compile Include="System\Text\RegularExpressions\RegexCode.cs" />
<Compile Include="System\Text\RegularExpressions\RegexCollectionDebuggerProxy.cs" />
<Compile Include="System\Text\RegularExpressions\RegexCompilationInfo.cs" />
<Compile Include="System\Text\RegularExpressions\RegexFCD.cs" />
<Compile Include="System\Text\RegularExpressions\RegexGroup.cs" />
<Compile Include="System\Text\RegularExpressions\RegexGroupCollection.cs" />
<Compile Include="System\Text\RegularExpressions\RegexInterpreter.cs" />
<Compile Include="System\Text\RegularExpressions\RegexMatch.cs" />
<Compile Include="System\Text\RegularExpressions\RegexMatchCollection.cs" />
<Compile Include="System\Text\RegularExpressions\RegexMatchTimeoutException.cs" />
<Compile Include="System\Text\RegularExpressions\RegexNode.cs" />
<Compile Include="System\Text\RegularExpressions\RegexOptions.cs" />
<Compile Include="System\Text\RegularExpressions\RegexParser.cs" />
<Compile Include="System\Text\RegularExpressions\RegexPrefix.cs" />
<Compile Include="System\Text\RegularExpressions\RegexReplacement.cs" />
<Compile Include="System\Text\RegularExpressions\RegexRunner.cs" />
<Compile Include="System\Text\RegularExpressions\RegexRunnerFactory.cs" />
<Compile Include="System\Text\RegularExpressions\RegexTree.cs" />
<Compile Include="System\Text\RegularExpressions\RegexWriter.cs" />
<Compile Include="System\Text\RegularExpressions\RegexRunnerFactory.cs" />
<!-- Common or Common-branched source files -->
<Compile Include="$(CommonPath)\System\NotImplemented.cs">
<Link>Common\System\NotImplemented.cs</Link>
</Compile>
<Compile Include="$(CommonPath)\System\IO\StringBuilderCache.cs">
<Link>Common\System\IO\StringBuilderCache.cs</Link>
<Compile Include="$(CommonPath)\CoreLib\System\Text\StringBuilderCache.cs">
<Link>Common\System\Text\StringBuilderCache.cs</Link>
</Compile>
<Compile Include="$(CommonPath)\CoreLib\System\Collections\Generic\ValueListBuilder.cs">
<Link>Common\System\Collections\Generic\ValueListBuilder.cs</Link>
</Compile>
</ItemGroup>
<!-- Files that enable compiled feature -->
@@ -51,9 +63,11 @@
<Compile Include="System\Text\RegularExpressions\RegexLWCGCompiler.cs" />
</ItemGroup>
<ItemGroup>
<Reference Include="System.Buffers" />
<Reference Include="System.Collections" />
<Reference Include="System.Diagnostics.Debug" />
<Reference Include="System.Diagnostics.Tools" />
<Reference Include="System.Memory" />
<Reference Include="System.Resources.ResourceManager" />
<Reference Include="System.Runtime" />
<Reference Include="System.Runtime.Extensions" />
@@ -66,4 +80,4 @@
<Reference Include="System.Reflection.Primitives" />
</ItemGroup>
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
</Project>
</Project>

View File

@@ -0,0 +1,21 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Runtime.CompilerServices;
namespace System.Collections.Generic
{
/// <summary>
/// These public methods are required by RegexWriter.
/// </summary>
internal ref partial struct ValueListBuilder<T>
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public T Pop()
{
_pos--;
return _span[_pos];
}
}
}

View File

@@ -2,9 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections;
namespace System.Text.RegularExpressions
namespace System.Collections
{
internal static class HashtableExtensions
{
@@ -15,7 +13,7 @@ namespace System.Text.RegularExpressions
value = (T)table[key];
return true;
}
value = default(T);
value = default;
return false;
}
}

View File

@@ -0,0 +1,61 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
// Capture is just a location/length pair that indicates the
// location of a regular expression match. A single regexp
// search may return multiple Capture within each capturing
// RegexGroup.
namespace System.Text.RegularExpressions
{
/// <summary>
/// Represents the results from a single subexpression capture. The object represents
/// one substring for a single successful capture.
/// </summary>
public class Capture
{
internal Capture(string text, int index, int length)
{
Text = text;
Index = index;
Length = length;
}
/// <summary>
/// Returns the position in the original string where the first character of
/// captured substring was found.
/// </summary>
public int Index { get; private protected set; }
/// <summary>
/// Returns the length of the captured substring.
/// </summary>
public int Length { get; private protected set; }
/// <summary>
/// The original string
/// </summary>
internal string Text { get; private protected set; }
/// <summary>
/// Returns the value of this Regex Capture.
/// </summary>
public string Value => Text.Substring(Index, Length);
/// <summary>
/// Returns the substring that was matched.
/// </summary>
public override string ToString() => Value;
/// <summary>
/// The substring to the left of the capture
/// </summary>
internal ReadOnlySpan<char> GetLeftSubstring() => Text.AsSpan(0, Index);
/// <summary>
/// The substring to the right of the capture
/// </summary>
internal ReadOnlySpan<char> GetRightSubstring() => Text.AsSpan(Index + Length, Text.Length - Index - Length);
}
}

View File

@@ -21,8 +21,10 @@ namespace System.Text.RegularExpressions
/// to return the set of captures done by a single capturing group.
/// </summary>
[DebuggerDisplay("Count = {Count}")]
[DebuggerTypeProxy(typeof(RegexCollectionDebuggerProxy<Capture>))]
#if !MONO
[Serializable]
#endif
[DebuggerTypeProxy(typeof(CollectionDebuggerProxy<Capture>))]
public class CaptureCollection : IList<Capture>, IReadOnlyList<Capture>, IList
{
private readonly Group _group;
@@ -68,16 +70,24 @@ namespace System.Text.RegularExpressions
// first time a capture is accessed, compute them all
if (_captures == null)
{
_captures = new Capture[_capcount];
for (int j = 0; j < _capcount - 1; j++)
{
_captures[j] = new Capture(_group._text, _group._caps[j * 2], _group._caps[j * 2 + 1]);
}
ForceInitialized();
}
return _captures[i];
}
/// <summary>
/// Compute all captures
/// </summary>
internal void ForceInitialized()
{
_captures = new Capture[_capcount];
for (int j = 0; j < _capcount - 1; j++)
{
_captures[j] = new Capture(_group.Text, _group._caps[j * 2], _group._caps[j * 2 + 1]);
}
}
public bool IsSynchronized => false;
public object SyncRoot => _group;
@@ -110,12 +120,14 @@ namespace System.Text.RegularExpressions
int IList<Capture>.IndexOf(Capture item)
{
var comparer = EqualityComparer<Capture>.Default;
for (int i = 0; i < Count; i++)
{
if (comparer.Equals(this[i], item))
if (EqualityComparer<Capture>.Default.Equals(this[i], item))
{
return i;
}
}
return -1;
}

View File

@@ -7,16 +7,13 @@ using System.Diagnostics;
namespace System.Text.RegularExpressions
{
internal sealed class RegexCollectionDebuggerProxy<T>
internal sealed class CollectionDebuggerProxy<T>
{
private readonly ICollection<T> _collection;
public RegexCollectionDebuggerProxy(ICollection<T> collection)
public CollectionDebuggerProxy(ICollection<T> collection)
{
if (collection == null)
throw new ArgumentNullException(nameof(collection));
_collection = collection;
_collection = collection ?? throw new ArgumentNullException(nameof(collection));
}
[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
@@ -24,7 +21,7 @@ namespace System.Text.RegularExpressions
{
get
{
T[] items = new T[_collection.Count];
var items = new T[_collection.Count];
_collection.CopyTo(items, 0);
return items;
}

View File

@@ -10,9 +10,9 @@ namespace System.Text.RegularExpressions
private Func<RegexRunner, bool> _findFirstCharMethod;
private Action<RegexRunner> _initTrackCountMethod;
internal CompiledRegexRunner() { }
public CompiledRegexRunner() { }
internal void SetDelegates(Action<RegexRunner> go, Func<RegexRunner,bool> firstChar, Action<RegexRunner> trackCount)
public void SetDelegates(Action<RegexRunner> go, Func<RegexRunner,bool> firstChar, Action<RegexRunner> trackCount)
{
_goMethod = go;
_findFirstCharMethod = firstChar;

View File

@@ -2,6 +2,9 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
// This is the only concrete implementation of RegexRunnerFactory,
// but we cannot combine them due to RegexRunnerFactory having shipped public.
using System.Reflection.Emit;
namespace System.Text.RegularExpressions
@@ -12,7 +15,7 @@ namespace System.Text.RegularExpressions
private readonly DynamicMethod _findFirstCharMethod;
private readonly DynamicMethod _initTrackCountMethod;
internal CompiledRegexRunnerFactory(DynamicMethod go, DynamicMethod firstChar, DynamicMethod trackCount)
public CompiledRegexRunnerFactory(DynamicMethod go, DynamicMethod firstChar, DynamicMethod trackCount)
{
_goMethod = go;
_findFirstCharMethod = firstChar;

View File

@@ -16,46 +16,28 @@ namespace System.Text.RegularExpressions
[Serializable]
public class Group : Capture
{
// the empty group object
internal static readonly Group s_emptyGroup = new Group(string.Empty, Array.Empty<int>(), 0, string.Empty);
internal readonly int[] _caps;
internal int _capcount;
internal CaptureCollection _capcoll;
internal readonly string _name;
internal Group(string text, int[] caps, int capcount, string name)
: base(text, capcount == 0 ? 0 : caps[(capcount - 1) * 2],
: base(text, capcount == 0 ? 0 : caps[(capcount - 1) * 2],
capcount == 0 ? 0 : caps[(capcount * 2) - 1])
{
_caps = caps;
_capcount = capcount;
_name = name;
Name = name;
}
/// <summary>
/// Indicates whether the match is successful.
/// </summary>
public bool Success
{
get
{
return _capcount != 0;
}
}
public bool Success => _capcount != 0;
public string Name
{
get
{
return _name;
}
}
public string Name { get; }
/*
* The collection of all captures for this group
*/
/// <summary>
/// Returns a collection of all the captures matched by the capturing
/// group, in innermost-leftmost-first order (or innermost-rightmost-first order if
@@ -72,9 +54,6 @@ namespace System.Text.RegularExpressions
}
}
/*
* Convert to a thread-safe object by precomputing cache contents
*/
/// <summary>
/// Returns a Group object equivalent to the one supplied that is safe to share between
/// multiple threads.
@@ -85,14 +64,11 @@ namespace System.Text.RegularExpressions
throw new ArgumentNullException(nameof(inner));
// force Captures to be computed.
CaptureCollection capcoll;
Capture dummy;
capcoll = inner.Captures;
if (inner._capcount > 0)
dummy = capcoll[0];
CaptureCollection capcoll = inner.Captures;
if (inner.Success)
{
capcoll.ForceInitialized();
}
return inner;
}

View File

@@ -16,8 +16,10 @@ namespace System.Text.RegularExpressions
/// to return the set of captures done by a single capturing group.
/// </summary>
[DebuggerDisplay("Count = {Count}")]
[DebuggerTypeProxy(typeof(RegexCollectionDebuggerProxy<Group>))]
#if MONO
[Serializable]
#endif
[DebuggerTypeProxy(typeof(CollectionDebuggerProxy<Group>))]
public class GroupCollection : IList<Group>, IReadOnlyList<Group>, IList
{
private readonly Match _match;
@@ -56,8 +58,7 @@ namespace System.Text.RegularExpressions
{
if (_captureMap != null)
{
int groupNumImpl;
if (_captureMap.TryGetValue(groupnum, out groupNumImpl))
if (_captureMap.TryGetValue(groupnum, out int groupNumImpl))
{
return GetGroupImpl(groupNumImpl);
}
@@ -86,7 +87,7 @@ namespace System.Text.RegularExpressions
for (int i = 0; i < _groups.Length; i++)
{
string groupname = _match._regex.GroupNameFromNumber(i + 1);
_groups[i] = new Group(_match._text, _match._matches[i + 1], _match._matchcount[i + 1], groupname);
_groups[i] = new Group(_match.Text, _match._matches[i + 1], _match._matchcount[i + 1], groupname);
}
}

View File

@@ -36,7 +36,6 @@ namespace System.Text.RegularExpressions
[Serializable]
public class Match : Group
{
internal static readonly Match s_empty = new Match(null, 1, string.Empty, 0, 0, 0);
internal GroupCollection _groupcoll;
// input to the match
@@ -52,23 +51,11 @@ namespace System.Text.RegularExpressions
internal bool _balancing; // whether we've done any balancing with this match. If we
// have done balancing, we'll need to do extra work in Tidy().
/// <summary>
/// Returns an empty Match object.
/// </summary>
public static Match Empty
{
get
{
return s_empty;
}
}
internal Match(Regex regex, int capcount, string text, int begpos, int len, int startpos)
: base(text, new int[2], 0, "0")
{
_regex = regex;
_matchcount = new int[capcount];
_matches = new int[capcount][];
_matches[0] = _caps;
_textbeg = begpos;
@@ -77,17 +64,19 @@ namespace System.Text.RegularExpressions
_balancing = false;
// No need for an exception here. This is only called internally, so we'll use an Assert instead
System.Diagnostics.Debug.Assert(!(_textbeg < 0 || _textstart < _textbeg || _textend < _textstart || _text.Length < _textend),
System.Diagnostics.Debug.Assert(!(_textbeg < 0 || _textstart < _textbeg || _textend < _textstart || Text.Length < _textend),
"The parameters are out of range.");
}
/*
* Nonpublic set-text method
*/
/// <summary>
/// Returns an empty Match object.
/// </summary>
public static Match Empty { get; } = new Match(null, 1, string.Empty, 0, 0, 0);
internal virtual void Reset(Regex regex, string text, int textbeg, int textend, int textstart)
{
_regex = regex;
_text = text;
Text = text;
_textbeg = textbeg;
_textend = textend;
_textstart = textstart;
@@ -121,7 +110,7 @@ namespace System.Text.RegularExpressions
if (_regex == null)
return this;
return _regex.Run(false, _length, _text, _textbeg, _textend - _textbeg, _textpos);
return _regex.Run(false, Length, Text, _textbeg, _textend - _textbeg, _textpos);
}
/// <summary>
@@ -131,29 +120,20 @@ namespace System.Text.RegularExpressions
/// </summary>
public virtual string Result(string replacement)
{
RegexReplacement repl;
if (replacement == null)
throw new ArgumentNullException(nameof(replacement));
if (_regex == null)
throw new NotSupportedException(SR.NoResultOnFailed);
repl = (RegexReplacement)_regex._replref.Get();
if (repl == null || !repl.Pattern.Equals(replacement))
{
repl = RegexParser.ParseReplacement(replacement, _regex.caps, _regex.capsize, _regex.capnames, _regex.roptions);
_regex._replref.Cache(repl);
}
// Gets the weakly cached replacement helper or creates one if there isn't one already.
RegexReplacement repl = RegexReplacement.GetOrCreate(_regex._replref, replacement, _regex.caps, _regex.capsize,
_regex.capnames, _regex.roptions);
return repl.Replacement(this);
}
/*
* Used by the replacement code
*/
internal virtual string GroupToStringImpl(int groupnum)
internal virtual ReadOnlySpan<char> GroupToStringImpl(int groupnum)
{
int c = _matchcount[groupnum];
if (c == 0)
@@ -161,26 +141,18 @@ namespace System.Text.RegularExpressions
int[] matches = _matches[groupnum];
return _text.Substring(matches[(c - 1) * 2], matches[(c * 2) - 1]);
return Text.AsSpan(matches[(c - 1) * 2], matches[(c * 2) - 1]);
}
/*
* Used by the replacement code
*/
internal string LastGroupToStringImpl()
internal ReadOnlySpan<char> LastGroupToStringImpl()
{
return GroupToStringImpl(_matchcount.Length - 1);
}
/*
* Convert to a thread-safe object by precomputing cache contents
*/
/// <summary>
/// Returns a Match instance equivalent to the one supplied that is safe to share
/// between multiple threads.
/// </summary>
public static Match Synchronized(Match inner)
{
if (inner == null)
@@ -201,9 +173,9 @@ namespace System.Text.RegularExpressions
return inner;
}
/*
* Nonpublic builder: add a capture to the group specified by "cap"
*/
/// <summary>
/// Adds a capture to the group specified by "cap"
/// </summary>
internal virtual void AddMatch(int cap, int start, int len)
{
int capcount;
@@ -236,14 +208,11 @@ namespace System.Text.RegularExpressions
*/
internal virtual void BalanceMatch(int cap)
{
int capcount;
int target;
_balancing = true;
// we'll look at the last capture first
capcount = _matchcount[cap];
target = capcount * 2 - 2;
int capcount = _matchcount[cap];
int target = capcount * 2 - 2;
// first see if it is negative, and therefore is a reference to the next available
// capture group for balancing. If it is, we'll reset target to point to that capture.
@@ -260,25 +229,25 @@ namespace System.Text.RegularExpressions
AddMatch(cap, -3 - target, -4 - target /* == -3 - (target + 1) */ );
}
/*
* Nonpublic builder: removes a group match by capnum
*/
/// <summary>
/// Removes a group match by capnum
/// </summary>
internal virtual void RemoveMatch(int cap)
{
_matchcount[cap]--;
}
/*
* Nonpublic: tells if a group was matched by capnum
*/
/// <summary>
/// Tells if a group was matched by capnum
/// </summary>
internal virtual bool IsMatched(int cap)
{
return cap < _matchcount.Length && _matchcount[cap] > 0 && _matches[cap][_matchcount[cap] * 2 - 1] != (-3 + 1);
}
/*
* Nonpublic: returns the index of the last specified matched group by capnum
*/
/// <summary>
/// Returns the index of the last specified matched group by capnum
/// </summary>
internal virtual int MatchIndex(int cap)
{
int i = _matches[cap][_matchcount[cap] * 2 - 2];
@@ -288,9 +257,9 @@ namespace System.Text.RegularExpressions
return _matches[cap][-3 - i];
}
/*
* Nonpublic: returns the length of the last specified matched group by capnum
*/
/// <summary>
/// Returns the length of the last specified matched group by capnum
/// </summary>
internal virtual int MatchLength(int cap)
{
int i = _matches[cap][_matchcount[cap] * 2 - 1];
@@ -300,16 +269,14 @@ namespace System.Text.RegularExpressions
return _matches[cap][-3 - i];
}
/*
* Nonpublic: tidy the match so that it can be used as an immutable result
*/
/// <summary>
/// Tidy the match so that it can be used as an immutable result
/// </summary>
internal virtual void Tidy(int textpos)
{
int[] interval;
interval = _matches[0];
_index = interval[0];
_length = interval[1];
int[] interval = _matches[0];
Index = interval[0];
Length = interval[1];
_textpos = textpos;
_capcount = _matchcount[0];
@@ -387,7 +354,7 @@ namespace System.Text.RegularExpressions
string text = "";
if (_matches[i][j * 2] >= 0)
text = _text.Substring(_matches[i][j * 2], _matches[i][j * 2 + 1]);
text = Text.Substring(_matches[i][j * 2], _matches[i][j * 2 + 1]);
System.Diagnostics.Debug.WriteLine(" (" + _matches[i][j * 2].ToString(CultureInfo.InvariantCulture) + "," + _matches[i][j * 2 + 1].ToString(CultureInfo.InvariantCulture) + ") " + text);
}
@@ -396,23 +363,16 @@ namespace System.Text.RegularExpressions
#endif
}
/*
* MatchSparse is for handling the case where slots are
* sparsely arranged (e.g., if somebody says use slot 100000)
*/
/// <summary>
/// MatchSparse is for handling the case where slots are sparsely arranged (e.g., if somebody says use slot 100000)
/// </summary>
internal class MatchSparse : Match
{
// the lookup hashtable
new internal Hashtable _caps;
new internal readonly Hashtable _caps;
/*
* Nonpublic constructor
*/
internal MatchSparse(Regex regex, Hashtable caps, int capcount,
string text, int begpos, int len, int startpos)
: base(regex, capcount, text, begpos, len, startpos)
internal MatchSparse(Regex regex, Hashtable caps, int capcount, string text, int begpos, int len, int startpos)
: base(regex, capcount, text, begpos, len, startpos)
{
_caps = caps;
}

View File

@@ -21,8 +21,10 @@ namespace System.Text.RegularExpressions
/// names in a regular expression.
/// </summary>
[DebuggerDisplay("Count = {Count}")]
[DebuggerTypeProxy(typeof(RegexCollectionDebuggerProxy<Match>))]
#if MONO
[Serializable]
#endif
[DebuggerTypeProxy(typeof(CollectionDebuggerProxy<Match>))]
public class MatchCollection : IList<Match>, IReadOnlyList<Match>, IList
{
private readonly Regex _regex;
@@ -113,7 +115,7 @@ namespace System.Text.RegularExpressions
_matches.Add(match);
_prevlen = match._length;
_prevlen = match.Length;
_startat = match._textpos;
} while (_matches.Count <= i);

View File

@@ -0,0 +1,88 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Threading;
namespace System.Text.RegularExpressions
{
/// <summary>
/// Used to cache one exclusive runner reference
/// </summary>
internal sealed class ExclusiveReference
{
private RegexRunner _ref;
private RegexRunner _obj;
private volatile int _locked;
/// <summary>
/// Return an object and grab an exclusive lock.
///
/// If the exclusive lock can't be obtained, null is returned;
/// if the object can't be returned, the lock is released.
/// </summary>
public RegexRunner Get()
{
// try to obtain the lock
if (0 == Interlocked.Exchange(ref _locked, 1))
{
// grab reference
RegexRunner obj = _ref;
// release the lock and return null if no reference
if (obj == null)
{
_locked = 0;
return null;
}
// remember the reference and keep the lock
_obj = obj;
return obj;
}
return null;
}
/// <summary>
/// Release an object back to the cache.
///
/// If the object is the one that's under lock, the lock is released.
/// If there is no cached object, then the lock is obtained and the object is placed in the cache.
/// </summary>
public void Release(RegexRunner obj)
{
if (obj == null)
throw new ArgumentNullException(nameof(obj));
// if this reference owns the lock, release it
if (_obj == obj)
{
_obj = null;
_locked = 0;
return;
}
// if no reference owns the lock, try to cache this reference
if (_obj == null)
{
// try to obtain the lock
if (0 == Interlocked.Exchange(ref _locked, 1))
{
// if there's really no reference, cache this reference
if (_ref == null)
_ref = obj;
// release the lock
_locked = 0;
return;
}
}
}
}
}

View File

@@ -0,0 +1,303 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using SysDebug = System.Diagnostics.Debug; // as Regex.Debug
using System.Collections.Generic;
using System.Collections;
using System.Runtime.CompilerServices;
using System.Threading;
namespace System.Text.RegularExpressions
{
public partial class Regex
{
private const int CacheDictionarySwitchLimit = 10;
private static int s_cacheSize = 15;
// the cache of code and factories that are currently loaded:
// Dictionary for large cache
private static readonly Dictionary<CachedCodeEntryKey, CachedCodeEntry> s_cache = new Dictionary<CachedCodeEntryKey, CachedCodeEntry>(s_cacheSize);
// linked list for MRU and for small cache
private static int s_cacheCount = 0;
private static CachedCodeEntry s_cacheFirst;
private static CachedCodeEntry s_cacheLast;
public static int CacheSize
{
get
{
return s_cacheSize;
}
set
{
if (value < 0)
throw new ArgumentOutOfRangeException(nameof(value));
lock (s_cache)
{
s_cacheSize = value; // not to allow other thread to change it while we use cache
while (s_cacheCount > s_cacheSize)
{
CachedCodeEntry last = s_cacheLast;
if (s_cacheCount >= CacheDictionarySwitchLimit)
{
SysDebug.Assert(s_cache.ContainsKey(last.Key));
s_cache.Remove(last.Key);
}
// update linked list:
s_cacheLast = last.Next;
if (last.Next != null)
{
SysDebug.Assert(s_cacheFirst != null);
SysDebug.Assert(s_cacheFirst != last);
SysDebug.Assert(last.Next.Previous == last);
last.Next.Previous = null;
}
else // last one removed
{
SysDebug.Assert(s_cacheFirst == last);
s_cacheFirst = null;
}
s_cacheCount--;
}
}
}
}
/// <summary>
/// Find cache based on options+pattern+culture and optionally add new cache if not found
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private CachedCodeEntry GetCachedCode(CachedCodeEntryKey key, bool isToAdd)
{
// to avoid lock:
CachedCodeEntry first = s_cacheFirst;
if (first?.Key == key)
return first;
if (s_cacheSize == 0)
return null;
return GetCachedCodeEntryInternal(key, isToAdd);
}
private CachedCodeEntry GetCachedCodeEntryInternal(CachedCodeEntryKey key, bool isToAdd)
{
lock (s_cache)
{
// first look for it in the cache and move it to the head
CachedCodeEntry entry = LookupCachedAndPromote(key);
// it wasn't in the cache, so we'll add a new one
if (entry == null && isToAdd && s_cacheSize != 0) // check cache size again in case it changed
{
entry = new CachedCodeEntry(key, capnames, capslist, _code, caps, capsize, _runnerref, _replref);
// put first in linked list:
if (s_cacheFirst != null)
{
SysDebug.Assert(s_cacheFirst.Next == null);
s_cacheFirst.Next = entry;
entry.Previous = s_cacheFirst;
}
s_cacheFirst = entry;
s_cacheCount++;
if (s_cacheCount >= CacheDictionarySwitchLimit)
{
if (s_cacheCount == CacheDictionarySwitchLimit)
FillCacheDictionary();
else
s_cache.Add(key, entry);
SysDebug.Assert(s_cacheCount == s_cache.Count);
}
// update last in linked list:
if (s_cacheLast == null)
{
s_cacheLast = entry;
}
else if (s_cacheCount > s_cacheSize) // remove last
{
CachedCodeEntry last = s_cacheLast;
if (s_cacheCount >= CacheDictionarySwitchLimit)
{
SysDebug.Assert(s_cache[last.Key] == s_cacheLast);
s_cache.Remove(last.Key);
}
SysDebug.Assert(last.Previous == null);
SysDebug.Assert(last.Next != null);
SysDebug.Assert(last.Next.Previous == last);
last.Next.Previous = null;
s_cacheLast = last.Next;
s_cacheCount--;
}
}
return entry;
}
}
private void FillCacheDictionary()
{
s_cache.Clear();
CachedCodeEntry next = s_cacheFirst;
while (next != null)
{
s_cache.Add(next.Key, next);
next = next.Previous;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)] // Unprofitable inline - JIT overly pessimistic
private static bool TryGetCacheValue(CachedCodeEntryKey key, out CachedCodeEntry entry)
{
if (s_cacheCount >= CacheDictionarySwitchLimit)
{
SysDebug.Assert((s_cacheFirst != null && s_cacheLast != null && s_cache.Count > 0) ||
(s_cacheFirst == null && s_cacheLast == null && s_cache.Count == 0),
"Linked list and Dict should be synchronized");
return s_cache.TryGetValue(key, out entry);
}
return TryGetCacheValueSmall(key, out entry);
}
private static bool TryGetCacheValueSmall(CachedCodeEntryKey key, out CachedCodeEntry entry)
{
entry = s_cacheFirst?.Previous; // first already checked
while (entry != null)
{
if (entry.Key == key)
return true;
entry = entry.Previous;
}
return false;
}
private static CachedCodeEntry LookupCachedAndPromote(CachedCodeEntryKey key)
{
SysDebug.Assert(Monitor.IsEntered(s_cache));
if (s_cacheFirst?.Key == key) // again check this as could have been promoted by other thread
return s_cacheFirst;
if (TryGetCacheValue(key, out CachedCodeEntry entry))
{
// promote:
SysDebug.Assert(s_cacheFirst != entry, "key should not get s_livecode_first");
SysDebug.Assert(s_cacheFirst != null, "as Dict has at least one");
SysDebug.Assert(s_cacheFirst.Next == null);
SysDebug.Assert(s_cacheFirst.Previous != null);
SysDebug.Assert(entry.Next != null, "not first so Next should exist");
SysDebug.Assert(entry.Next.Previous == entry);
if (s_cacheLast == entry)
{
SysDebug.Assert(entry.Previous == null, "last");
s_cacheLast = entry.Next;
}
else
{
SysDebug.Assert(entry.Previous != null, "in middle");
SysDebug.Assert(entry.Previous.Next == entry);
entry.Previous.Next = entry.Next;
}
entry.Next.Previous = entry.Previous;
s_cacheFirst.Next = entry;
entry.Previous = s_cacheFirst;
entry.Next = null;
s_cacheFirst = entry;
}
return entry;
}
/// <summary>
/// Used as a key for CacheCodeEntry
/// </summary>
internal readonly struct CachedCodeEntryKey : IEquatable<CachedCodeEntryKey>
{
private readonly RegexOptions _options;
private readonly string _cultureKey;
private readonly string _pattern;
public CachedCodeEntryKey(RegexOptions options, string cultureKey, string pattern)
{
SysDebug.Assert(cultureKey != null, "Culture must be provided");
SysDebug.Assert(pattern != null, "Pattern must be provided");
_options = options;
_cultureKey = cultureKey;
_pattern = pattern;
}
public override bool Equals(object obj)
{
return obj is CachedCodeEntryKey && Equals((CachedCodeEntryKey)obj);
}
public bool Equals(CachedCodeEntryKey other)
{
return _pattern.Equals(other._pattern) && _options == other._options && _cultureKey.Equals(other._cultureKey);
}
public static bool operator ==(CachedCodeEntryKey left, CachedCodeEntryKey right)
{
return left.Equals(right);
}
public static bool operator !=(CachedCodeEntryKey left, CachedCodeEntryKey right)
{
return !left.Equals(right);
}
public override int GetHashCode()
{
return ((int)_options) ^ _cultureKey.GetHashCode() ^ _pattern.GetHashCode();
}
}
/// <summary>
/// Used to cache byte codes
/// </summary>
internal sealed class CachedCodeEntry
{
public CachedCodeEntry Next;
public CachedCodeEntry Previous;
public readonly CachedCodeEntryKey Key;
public RegexCode Code;
public readonly Hashtable Caps;
public readonly Hashtable Capnames;
public readonly string[] Capslist;
#if FEATURE_COMPILED
public RegexRunnerFactory Factory;
#endif
public readonly int Capsize;
public readonly ExclusiveReference Runnerref;
public readonly WeakReference<RegexReplacement> ReplRef;
public CachedCodeEntry(CachedCodeEntryKey key, Hashtable capnames, string[] capslist, RegexCode code,
Hashtable caps, int capsize, ExclusiveReference runner, WeakReference<RegexReplacement> replref)
{
Key = key;
Capnames = capnames;
Capslist = capslist;
Code = code;
Caps = caps;
Capsize = capsize;
Runnerref = runner;
ReplRef = replref;
}
#if FEATURE_COMPILED
public void AddCompiled(RegexRunnerFactory factory)
{
Factory = factory;
Code = null;
}
#endif
}
}
}

View File

@@ -0,0 +1,184 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace System.Text.RegularExpressions
{
public partial class Regex
{
/// <summary>
/// Searches the input string for one or more occurrences of the text supplied in the given pattern.
/// </summary>
public static bool IsMatch(string input, string pattern)
{
return IsMatch(input, pattern, RegexOptions.None, s_defaultMatchTimeout);
}
/// <summary>
/// Searches the input string for one or more occurrences of the text
/// supplied in the pattern parameter with matching options supplied in the options
/// parameter.
/// </summary>
public static bool IsMatch(string input, string pattern, RegexOptions options)
{
return IsMatch(input, pattern, options, s_defaultMatchTimeout);
}
public static bool IsMatch(string input, string pattern, RegexOptions options, TimeSpan matchTimeout)
{
return new Regex(pattern, options, matchTimeout, true).IsMatch(input);
}
/*
* Returns true if the regex finds a match within the specified string
*/
/// <summary>
/// Searches the input string for one or more matches using the previous pattern,
/// options, and starting position.
/// </summary>
public bool IsMatch(string input)
{
if (input == null)
throw new ArgumentNullException(nameof(input));
return IsMatch(input, UseOptionR() ? input.Length : 0);
}
/*
* Returns true if the regex finds a match after the specified position
* (proceeding leftward if the regex is leftward and rightward otherwise)
*/
/// <summary>
/// Searches the input string for one or more matches using the previous pattern and options,
/// with a new starting position.
/// </summary>
public bool IsMatch(string input, int startat)
{
if (input == null)
throw new ArgumentNullException(nameof(input));
return (null == Run(true, -1, input, 0, input.Length, startat));
}
/// <summary>
/// Searches the input string for one or more occurrences of the text
/// supplied in the pattern parameter.
/// </summary>
public static Match Match(string input, string pattern)
{
return Match(input, pattern, RegexOptions.None, s_defaultMatchTimeout);
}
/// <summary>
/// Searches the input string for one or more occurrences of the text
/// supplied in the pattern parameter. Matching is modified with an option
/// string.
/// </summary>
public static Match Match(string input, string pattern, RegexOptions options)
{
return Match(input, pattern, options, s_defaultMatchTimeout);
}
public static Match Match(string input, string pattern, RegexOptions options, TimeSpan matchTimeout)
{
return new Regex(pattern, options, matchTimeout, true).Match(input);
}
/*
* Finds the first match for the regular expression starting at the beginning
* of the string (or at the end of the string if the regex is leftward)
*/
/// <summary>
/// Matches a regular expression with a string and returns
/// the precise result as a RegexMatch object.
/// </summary>
public Match Match(string input)
{
if (input == null)
throw new ArgumentNullException(nameof(input));
return Match(input, UseOptionR() ? input.Length : 0);
}
/*
* Finds the first match, starting at the specified position
*/
/// <summary>
/// Matches a regular expression with a string and returns
/// the precise result as a RegexMatch object.
/// </summary>
public Match Match(string input, int startat)
{
if (input == null)
throw new ArgumentNullException(nameof(input));
return Run(false, -1, input, 0, input.Length, startat);
}
/*
* Finds the first match, restricting the search to the specified interval of
* the char array.
*/
/// <summary>
/// Matches a regular expression with a string and returns the precise result as a
/// RegexMatch object.
/// </summary>
public Match Match(string input, int beginning, int length)
{
if (input == null)
throw new ArgumentNullException(nameof(input));
return Run(false, -1, input, beginning, length, UseOptionR() ? beginning + length : beginning);
}
/// <summary>
/// Returns all the successful matches as if Match were called iteratively numerous times.
/// </summary>
public static MatchCollection Matches(string input, string pattern)
{
return Matches(input, pattern, RegexOptions.None, s_defaultMatchTimeout);
}
/// <summary>
/// Returns all the successful matches as if Match were called iteratively numerous times.
/// </summary>
public static MatchCollection Matches(string input, string pattern, RegexOptions options)
{
return Matches(input, pattern, options, s_defaultMatchTimeout);
}
public static MatchCollection Matches(string input, string pattern, RegexOptions options, TimeSpan matchTimeout)
{
return new Regex(pattern, options, matchTimeout, true).Matches(input);
}
/*
* Finds the first match for the regular expression starting at the beginning
* of the string Enumerator(or at the end of the string if the regex is leftward)
*/
/// <summary>
/// Returns all the successful matches as if Match was called iteratively numerous times.
/// </summary>
public MatchCollection Matches(string input)
{
if (input == null)
throw new ArgumentNullException(nameof(input));
return Matches(input, UseOptionR() ? input.Length : 0);
}
/*
* Finds the first match, starting at the specified position
*/
/// <summary>
/// Returns all the successful matches as if Match was called iteratively numerous times.
/// </summary>
public MatchCollection Matches(string input, int startat)
{
if (input == null)
throw new ArgumentNullException(nameof(input));
return new MatchCollection(this, input, 0, input.Length, startat);
}
}
}

View File

@@ -0,0 +1,231 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace System.Text.RegularExpressions
{
// Callback class
public delegate string MatchEvaluator(Match match);
public partial class Regex
{
/// <summary>
/// Replaces all occurrences of the pattern with the <paramref name="replacement"/> pattern, starting at
/// the first character in the input string.
/// </summary>
public static string Replace(string input, string pattern, string replacement)
{
return Replace(input, pattern, replacement, RegexOptions.None, s_defaultMatchTimeout);
}
/// <summary>
/// Replaces all occurrences of
/// the <paramref name="pattern "/>with the <paramref name="replacement "/>
/// pattern, starting at the first character in the input string.
/// </summary>
public static string Replace(string input, string pattern, string replacement, RegexOptions options)
{
return Replace(input, pattern, replacement, options, s_defaultMatchTimeout);
}
public static string Replace(string input, string pattern, string replacement, RegexOptions options, TimeSpan matchTimeout)
{
return new Regex(pattern, options, matchTimeout, true).Replace(input, replacement);
}
/// <summary>
/// Replaces all occurrences of the previously defined pattern with the
/// <paramref name="replacement"/> pattern, starting at the first character in the
/// input string.
/// </summary>
public string Replace(string input, string replacement)
{
if (input == null)
throw new ArgumentNullException(nameof(input));
return Replace(input, replacement, -1, UseOptionR() ? input.Length : 0);
}
/// <summary>
/// Replaces all occurrences of the previously defined pattern with the
/// <paramref name="replacement"/> pattern, starting at the first character in the
/// input string.
/// </summary>
public string Replace(string input, string replacement, int count)
{
if (input == null)
throw new ArgumentNullException(nameof(input));
return Replace(input, replacement, count, UseOptionR() ? input.Length : 0);
}
/// <summary>
/// Replaces all occurrences of the previously defined pattern with the
/// <paramref name="replacement"/> pattern, starting at the character position
/// <paramref name="startat"/>.
/// </summary>
public string Replace(string input, string replacement, int count, int startat)
{
if (input == null)
throw new ArgumentNullException(nameof(input));
if (replacement == null)
throw new ArgumentNullException(nameof(replacement));
// Gets the weakly cached replacement helper or creates one if there isn't one already.
RegexReplacement repl = RegexReplacement.GetOrCreate(_replref, replacement, caps, capsize, capnames, roptions);
return repl.Replace(this, input, count, startat);
}
/// <summary>
/// Replaces all occurrences of the <paramref name="pattern"/> with the recent
/// replacement pattern.
/// </summary>
public static string Replace(string input, string pattern, MatchEvaluator evaluator)
{
return Replace(input, pattern, evaluator, RegexOptions.None, s_defaultMatchTimeout);
}
/// <summary>
/// Replaces all occurrences of the <paramref name="pattern"/> with the recent
/// replacement pattern, starting at the first character.
/// </summary>
public static string Replace(string input, string pattern, MatchEvaluator evaluator, RegexOptions options)
{
return Replace(input, pattern, evaluator, options, s_defaultMatchTimeout);
}
public static string Replace(string input, string pattern, MatchEvaluator evaluator, RegexOptions options, TimeSpan matchTimeout)
{
return new Regex(pattern, options, matchTimeout, true).Replace(input, evaluator);
}
/// <summary>
/// Replaces all occurrences of the previously defined pattern with the recent
/// replacement pattern, starting at the first character position.
/// </summary>
public string Replace(string input, MatchEvaluator evaluator)
{
if (input == null)
throw new ArgumentNullException(nameof(input));
return Replace(input, evaluator, -1, UseOptionR() ? input.Length : 0);
}
/// <summary>
/// Replaces all occurrences of the previously defined pattern with the recent
/// replacement pattern, starting at the first character position.
/// </summary>
public string Replace(string input, MatchEvaluator evaluator, int count)
{
if (input == null)
throw new ArgumentNullException(nameof(input));
return Replace(input, evaluator, count, UseOptionR() ? input.Length : 0);
}
/// <summary>
/// Replaces all occurrences of the previously defined pattern with the recent
/// replacement pattern, starting at the character position
/// <paramref name="startat"/>.
/// </summary>
public string Replace(string input, MatchEvaluator evaluator, int count, int startat)
{
if (input == null)
throw new ArgumentNullException(nameof(input));
return Replace(evaluator, this, input, count, startat);
}
/// <summary>
/// Replaces all occurrences of the regex in the string with the
/// replacement evaluator.
///
/// Note that the special case of no matches is handled on its own:
/// with no matches, the input string is returned unchanged.
/// The right-to-left case is split out because StringBuilder
/// doesn't handle right-to-left string building directly very well.
/// </summary>
private static string Replace(MatchEvaluator evaluator, Regex regex, string input, int count, int startat)
{
if (evaluator == null)
throw new ArgumentNullException(nameof(evaluator));
if (count < -1)
throw new ArgumentOutOfRangeException(nameof(count), SR.CountTooSmall);
if (startat < 0 || startat > input.Length)
throw new ArgumentOutOfRangeException(nameof(startat), SR.BeginIndexNotNegative);
if (count == 0)
return input;
Match match = regex.Match(input, startat);
if (!match.Success)
{
return input;
}
else
{
StringBuilder sb = StringBuilderCache.Acquire();
if (!regex.RightToLeft)
{
int prevat = 0;
do
{
if (match.Index != prevat)
sb.Append(input, prevat, match.Index - prevat);
prevat = match.Index + match.Length;
sb.Append(evaluator(match));
if (--count == 0)
break;
match = match.NextMatch();
} while (match.Success);
if (prevat < input.Length)
sb.Append(input, prevat, input.Length - prevat);
}
else
{
List<string> al = new List<string>();
int prevat = input.Length;
do
{
if (match.Index + match.Length != prevat)
al.Add(input.Substring(match.Index + match.Length, prevat - match.Index - match.Length));
prevat = match.Index;
al.Add(evaluator(match));
if (--count == 0)
break;
match = match.NextMatch();
} while (match.Success);
if (prevat > 0)
sb.Append(input, 0, prevat);
for (int i = al.Count - 1; i >= 0; i--)
{
sb.Append(al[i]);
}
}
return StringBuilderCache.GetStringAndRelease(sb);
}
}
}
}

View File

@@ -0,0 +1,165 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
namespace System.Text.RegularExpressions
{
public partial class Regex
{
/// <summary>
/// Splits the <paramref name="input "/>string at the position defined
/// by <paramref name="pattern"/>.
/// </summary>
public static string[] Split(string input, string pattern)
{
return Split(input, pattern, RegexOptions.None, s_defaultMatchTimeout);
}
/// <summary>
/// Splits the <paramref name="input "/>string at the position defined by <paramref name="pattern"/>.
/// </summary>
public static string[] Split(string input, string pattern, RegexOptions options)
{
return Split(input, pattern, options, s_defaultMatchTimeout);
}
public static string[] Split(string input, string pattern, RegexOptions options, TimeSpan matchTimeout)
{
return new Regex(pattern, options, matchTimeout, true).Split(input);
}
/// <summary>
/// Splits the <paramref name="input"/> string at the position defined by a
/// previous pattern.
/// </summary>
public string[] Split(string input)
{
if (input == null)
throw new ArgumentNullException(nameof(input));
return Split(input, 0, UseOptionR() ? input.Length : 0);
}
/// <summary>
/// Splits the <paramref name="input"/> string at the position defined by a
/// previous pattern.
/// </summary>
public string[] Split(string input, int count)
{
if (input == null)
throw new ArgumentNullException(nameof(input));
return Split(this, input, count, UseOptionR() ? input.Length : 0);
}
/// <summary>
/// Splits the <paramref name="input"/> string at the position defined by a previous pattern.
/// </summary>
public string[] Split(string input, int count, int startat)
{
if (input == null)
throw new ArgumentNullException(nameof(input));
return Split(this, input, count, startat);
}
/// <summary>
/// Does a split. In the right-to-left case we reorder the
/// array to be forwards.
/// </summary>
private static string[] Split(Regex regex, string input, int count, int startat)
{
if (count < 0)
throw new ArgumentOutOfRangeException(nameof(count), SR.CountTooSmall);
if (startat < 0 || startat > input.Length)
throw new ArgumentOutOfRangeException(nameof(startat), SR.BeginIndexNotNegative);
string[] result;
if (count == 1)
{
result = new string[1];
result[0] = input;
return result;
}
count -= 1;
Match match = regex.Match(input, startat);
if (!match.Success)
{
result = new string[1];
result[0] = input;
return result;
}
else
{
List<string> al = new List<string>();
if (!regex.RightToLeft)
{
int prevat = 0;
for (; ; )
{
al.Add(input.Substring(prevat, match.Index - prevat));
prevat = match.Index + match.Length;
// add all matched capture groups to the list.
for (int i = 1; i < match.Groups.Count; i++)
{
if (match.IsMatched(i))
al.Add(match.Groups[i].ToString());
}
if (--count == 0)
break;
match = match.NextMatch();
if (!match.Success)
break;
}
al.Add(input.Substring(prevat, input.Length - prevat));
}
else
{
int prevat = input.Length;
for (; ; )
{
al.Add(input.Substring(match.Index + match.Length, prevat - match.Index - match.Length));
prevat = match.Index;
// add all matched capture groups to the list.
for (int i = 1; i < match.Groups.Count; i++)
{
if (match.IsMatched(i))
al.Add(match.Groups[i].ToString());
}
if (--count == 0)
break;
match = match.NextMatch();
if (!match.Success)
break;
}
al.Add(input.Substring(0, prevat));
al.Reverse(0, al.Count);
}
return al.ToArray();
}
}
}
}

View File

@@ -0,0 +1,104 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Threading;
namespace System.Text.RegularExpressions
{
public partial class Regex
{
// We need this because time is queried using Environment.TickCount for performance reasons
// (Environment.TickCount returns milliseconds as an int and cycles):
private static readonly TimeSpan s_maximumMatchTimeout = TimeSpan.FromMilliseconds(int.MaxValue - 1);
// During static initialisation of Regex we check
private const string DefaultMatchTimeout_ConfigKeyName = "REGEX_DEFAULT_MATCH_TIMEOUT";
// DefaultMatchTimeout specifies the match timeout to use if no other timeout was specified
// by one means or another. Typically, it is set to InfiniteMatchTimeout.
internal static readonly TimeSpan s_defaultMatchTimeout;
// InfiniteMatchTimeout specifies that match timeout is switched OFF. It allows for faster code paths
// compared to simply having a very large timeout.
// We do not want to ask users to use System.Threading.Timeout.InfiniteTimeSpan as a parameter because:
// (1) We do not want to imply any relation between having using a RegEx timeout and using multi-threading.
// (2) We do not want to require users to take ref to a contract assembly for threading just to use RegEx.
// There may in theory be a SKU that has RegEx, but no multithreading.
// We create a public Regex.InfiniteMatchTimeout constant, which for consistency uses the save underlying
// value as Timeout.InfiniteTimeSpan creating an implementation detail dependency only.
public static readonly TimeSpan InfiniteMatchTimeout = Timeout.InfiniteTimeSpan;
// timeout for the execution of this regex
protected internal TimeSpan internalMatchTimeout;
static Regex()
{
s_defaultMatchTimeout = InitDefaultMatchTimeout();
}
/// <summary>
/// The match timeout used by this Regex instance.
/// </summary>
public TimeSpan MatchTimeout => internalMatchTimeout;
// Note: "&lt;" is the XML entity for smaller ("<").
/// <summary>
/// Validates that the specified match timeout value is valid.
/// The valid range is <code>TimeSpan.Zero &lt; matchTimeout &lt;= Regex.MaximumMatchTimeout</code>.
/// </summary>
/// <param name="matchTimeout">The timeout value to validate.</param>
/// <exception cref="ArgumentOutOfRangeException">If the specified timeout is not within a valid range.
/// </exception>
protected internal static void ValidateMatchTimeout(TimeSpan matchTimeout)
{
if (InfiniteMatchTimeout == matchTimeout)
return;
// Change this to make sure timeout is not longer then Environment.Ticks cycle length:
if (TimeSpan.Zero < matchTimeout && matchTimeout <= s_maximumMatchTimeout)
return;
throw new ArgumentOutOfRangeException(nameof(matchTimeout));
}
/// <summary>
/// Specifies the default RegEx matching timeout value (i.e. the timeout that will be used if no
/// explicit timeout is specified).
/// The default is queried from the current <code>AppDomain</code>.
/// If the AddDomain's data value for that key is not a <code>TimeSpan</code> value or if it is outside the
/// valid range, an exception is thrown.
/// If the AddDomain's data value for that key is <code>null</code>, a fallback value is returned.
/// </summary>
/// <returns>The default RegEx matching timeout for this AppDomain</returns>
private static TimeSpan InitDefaultMatchTimeout()
{
// Query AppDomain
AppDomain ad = AppDomain.CurrentDomain;
object defaultMatchTimeoutObj = ad.GetData(DefaultMatchTimeout_ConfigKeyName);
// If no default is specified, use fallback
if (defaultMatchTimeoutObj == null)
{
return InfiniteMatchTimeout;
}
if (defaultMatchTimeoutObj is TimeSpan defaultMatchTimeOut)
{
// If default timeout is outside the valid range, throw. It will result in a TypeInitializationException:
try
{
ValidateMatchTimeout(defaultMatchTimeOut);
}
catch (ArgumentOutOfRangeException)
{
throw new ArgumentOutOfRangeException(SR.Format(SR.IllegalDefaultRegexMatchTimeoutInAppDomain, DefaultMatchTimeout_ConfigKeyName, defaultMatchTimeOut));
}
return defaultMatchTimeOut;
}
throw new InvalidCastException(SR.Format(SR.IllegalDefaultRegexMatchTimeoutInAppDomain, DefaultMatchTimeout_ConfigKeyName, defaultMatchTimeoutObj));
}
}
}

Some files were not shown because too many files have changed in this diff Show More