1 //------------------------------------------------------------------------------
2 // <copyright file="Regex.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 //------------------------------------------------------------------------------
7 // The Regex class represents a single compiled instance of a regular
10 namespace System.Text.RegularExpressions {
13 using System.Threading;
14 using System.Collections;
15 using System.Reflection;
17 using System.Reflection.Emit;
19 using System.Globalization;
20 using System.Security.Permissions;
21 using System.Runtime.CompilerServices;
22 using System.Collections.Generic;
23 using System.Diagnostics.CodeAnalysis;
26 using System.Runtime.Serialization;
27 using System.Runtime.Versioning;
33 /// Represents an immutable, compiled regular expression. Also
34 /// contains static methods that allow use of regular expressions without instantiating
35 /// a Regex explicitly.
47 // Fields used by precompiled regexes
48 protected internal string pattern;
50 protected internal RegexRunnerFactory factory; // if compiled, this is the RegexRunner subclass
52 internal RegexRunnerFactory factory; // if compiled, this is the RegexRunner subclass
55 protected internal RegexOptions roptions; // the top-level options from the options string
58 // *********** Match timeout fields { ***********
60 // We need this because time is queried using Environment.TickCount for performance reasons
61 // (Environment.TickCount returns millisecs as an int and cycles):
65 private static readonly TimeSpan MaximumMatchTimeout = TimeSpan.FromMilliseconds(Int32.MaxValue - 1);
67 // InfiniteMatchTimeout specifies that match timeout is switched OFF. It allows for faster code paths
68 // compared to simply having a very large timeout.
69 // We do not want to ask users to use System.Threading.Timeout.InfiniteTimeSpan as a parameter because:
70 // (1) We do not want to imply any relation between having using a RegEx timeout and using multi-threading.
71 // (2) We do not want to require users to take ref to a contract assembly for threading just to use RegEx.
72 // There may in theory be a SKU that has RegEx, but no multithreading.
73 // We create a public Regex.InfiniteMatchTimeout constant, which for consistency uses the save underlying
74 // value as Timeout.InfiniteTimeSpan creating an implementation detail dependency only.
75 #if !SILVERLIGHT || FEATURE_NETCORE
79 public static readonly TimeSpan InfiniteMatchTimeout =
81 new TimeSpan (0, 0, 0, 0, Timeout.Infinite);
83 Timeout.InfiniteTimeSpan;
86 internal static readonly TimeSpan InfiniteMatchTimeout = new TimeSpan(0, 0, 0, 0, Timeout.Infinite);
89 // All these protected internal fields in this class really should not be protected. The historic reason
90 // for this is that classes extending Regex that are generated via CompileToAssembly rely on the fact that
91 // these are accessible as protected in order to initialise them in the generated constructor of the
92 // extending class. We should update this initialisation logic to using a protected constructor, but until
93 // that is done we stick to the existing pattern however ugly it may be.
95 [OptionalField(VersionAdded = 2)]
100 TimeSpan internalMatchTimeout; // timeout for the execution of this regex
103 // During static initialisation of Regex we check
104 private const String DefaultMatchTimeout_ConfigKeyName = "REGEX_DEFAULT_MATCH_TIMEOUT";
107 // FallbackDefaultMatchTimeout specifies the match timeout to use if no other timeout was specified
108 // by one means or another. For now it is set to InfiniteMatchTimeout, meaning timeouts are OFF by
109 // default (for Dev12 we plan to set a positive value).
110 // Having this field is helpful to read the code as it makes it clear when we mean
111 // "default that is currently no-timeouts" and when we mean "actually no-timeouts".
112 // In Silverlight, DefaultMatchTimeout is always set to FallbackDefaultMatchTimeout,
113 // on desktop, DefaultMatchTimeout can be configured via AppDomain and falls back to
114 // FallbackDefaultMatchTimeout, if no AppDomain setting is present (see InitDefaultMatchTimeout()).
118 internal static readonly TimeSpan FallbackDefaultMatchTimeout = InfiniteMatchTimeout;
121 // DefaultMatchTimeout specifies the match timeout to use if no other timeout was specified
122 // by one means or another. Typically, it is set to InfiniteMatchTimeout in Dev 11
123 // (we plan to set a positive timeout in Dev12).
124 // Hosts (e.g.) ASP may set an AppDomain property via SetData to change the default value.
127 internal static readonly TimeSpan DefaultMatchTimeout = InitDefaultMatchTimeout();
129 internal static readonly TimeSpan DefaultMatchTimeout = FallbackDefaultMatchTimeout;
132 // *********** } match timeout fields ***********
136 internal Dictionary<Int32, Int32> caps; // if captures are sparse, this is the hashtable capnum->index
137 internal Dictionary<String, Int32> capnames; // if named captures are used, this maps names->index
139 // desktop build still uses non-generic collections for AppCompat with .NET Framework 3.5 pre-compiled assemblies
140 protected internal Hashtable caps;
141 protected internal Hashtable capnames;
143 protected internal String[] capslist; // if captures are sparse or named captures are used, this is the sorted list of names
144 protected internal int capsize; // the size of the capture array
146 internal ExclusiveReference runnerref; // cached runner
147 internal SharedReference replref; // cached parsed replacement pattern
148 internal RegexCode code; // if interpreted, this is the code for RegexIntepreter
149 internal bool refsInitialized = false;
151 internal static LinkedList<CachedCodeEntry> livecode = new LinkedList<CachedCodeEntry>();// the cached of code and factories that are currently loaded
152 internal static int cacheSize = 15;
154 internal const int MaxOptionShift = 10;
158 // If a compiled-to-assembly RegEx was generated using an earlier version, then internalMatchTimeout will be uninitialised.
160 // In distant future, when RegEx generated using pre Dev11 are not supported any more, we can remove this to aid performance:
162 this.internalMatchTimeout = DefaultMatchTimeout;
166 * Compiles and returns a Regex object corresponding to the given pattern
170 /// Creates and compiles a regular expression object for the specified regular
174 public Regex(String pattern)
175 : this(pattern, RegexOptions.None, DefaultMatchTimeout, false) {
179 * Returns a Regex object corresponding to the given pattern, compiled with
180 * the specified options.
184 /// Creates and compiles a regular expression object for the
185 /// specified regular expression
186 /// with options that modify the pattern.
189 public Regex(String pattern, RegexOptions options)
190 : this(pattern, options, DefaultMatchTimeout, false) {
193 #if !SILVERLIGHT || FEATURE_NETCORE
198 Regex(String pattern, RegexOptions options, TimeSpan matchTimeout)
199 : this(pattern, options, matchTimeout, false) {
202 private Regex(String pattern, RegexOptions options, TimeSpan matchTimeout, bool useCache) {
204 CachedCodeEntry cached = null;
205 string cultureKey = null;
208 throw new ArgumentNullException("pattern");
209 if (options < RegexOptions.None || ( ((int) options) >> MaxOptionShift) != 0)
210 throw new ArgumentOutOfRangeException("options");
211 if ((options & RegexOptions.ECMAScript) != 0
212 && (options & ~(RegexOptions.ECMAScript |
213 RegexOptions.IgnoreCase |
214 RegexOptions.Multiline |
215 #if !(SILVERLIGHT) || FEATURE_LEGACYNETCF
216 RegexOptions.Compiled |
218 RegexOptions.CultureInvariant
223 throw new ArgumentOutOfRangeException("options");
225 ValidateMatchTimeout(matchTimeout);
227 // Try to look up this regex in the cache. We do this regardless of whether useCache is true since there's
228 // really no reason not to.
229 if ((options & RegexOptions.CultureInvariant) != 0)
230 cultureKey = CultureInfo.InvariantCulture.ToString(); // "English (United States)"
232 cultureKey = CultureInfo.CurrentCulture.ToString();
234 String key = ((int) options).ToString(NumberFormatInfo.InvariantInfo) + ":" + cultureKey + ":" + pattern;
235 cached = LookupCachedAndUpdate(key);
237 this.pattern = pattern;
238 this.roptions = options;
240 this.internalMatchTimeout = matchTimeout;
242 if (cached == null) {
244 tree = RegexParser.Parse(pattern, roptions);
246 // Extract the relevant information
247 capnames = tree._capnames;
248 capslist = tree._capslist;
249 code = RegexWriter.Write(tree);
251 capsize = code._capsize;
253 InitializeReferences();
257 cached = CacheCode(key);
261 capnames = cached._capnames;
262 capslist = cached._capslist;
263 capsize = cached._capsize;
265 factory = cached._factory;
266 runnerref = cached._runnerref;
267 replref = cached._replref;
268 refsInitialized = true;
271 #if !(SILVERLIGHT || FULL_AOT_RUNTIME)
272 // if the compile option is set, then compile the code if it's not already
273 if (UseOptionC() && factory == null) {
274 factory = Compile(code, roptions);
276 if (useCache && cached != null)
277 cached.AddCompiled(factory);
285 * ISerializable constructor
287 protected Regex(SerializationInfo info, StreamingContext context)
288 : this(info.GetString("pattern"), (RegexOptions) info.GetInt32("options")) {
291 Int64 timeoutTicks = info.GetInt64("matchTimeout");
292 TimeSpan timeout = new TimeSpan(timeoutTicks);
293 ValidateMatchTimeout(timeout);
294 this.internalMatchTimeout = timeout;
295 } catch (SerializationException) {
296 // If this occurs, then assume that this object was serialised using a version
297 // before timeout was added. In that case just do not set a timeout
298 // (keep default value)
304 * ISerializable method
307 void ISerializable.GetObjectData(SerializationInfo si, StreamingContext context) {
308 si.AddValue("pattern", this.ToString());
309 si.AddValue("options", this.Options);
310 si.AddValue("matchTimeout", this.MatchTimeout.Ticks);
312 #endif // !SILVERLIGHT
314 //* Note: "<" is the XML entity for smaller ("<").
316 /// Validates that the specified match timeout value is valid.
317 /// The valid range is <code>TimeSpan.Zero < matchTimeout <= Regex.MaximumMatchTimeout</code>.
319 /// <param name="matchTimeout">The timeout value to validate.</param>
320 /// <exception cref="System.ArgumentOutOfRangeException">If the specified timeout is not within a valid range.
327 static void ValidateMatchTimeout(TimeSpan matchTimeout) {
329 if (InfiniteMatchTimeout == matchTimeout)
332 // Change this to make sure timeout is not longer then Environment.Ticks cycle length:
333 if (TimeSpan.Zero < matchTimeout && matchTimeout <= MaximumMatchTimeout)
336 throw new ArgumentOutOfRangeException("matchTimeout");
341 /// Specifies the default RegEx matching timeout value (i.e. the timeout that will be used if no
342 /// explicit timeout is specified).
343 /// The default is queried from the current <code>AppDomain</code> through <code>GetData</code> using
344 /// the key specified in <code>Regex.DefaultMatchTimeout_ConfigKeyName</code>. For that key, the
345 /// current <code>AppDomain</code> is expected to either return <code>null</code> or a <code>TimeSpan</code>
346 /// value specifying the default timeout within a valid range.
347 /// If the AddDomain's data value for that key is not a <code>TimeSpan</code> value or if it is outside the
348 /// valid range, an exception is thrown which will result in a <code>TypeInitializationException</code> for RegEx.
349 /// If the AddDomain's data value for that key is <code>null</code>, a fallback value is returned
350 /// (see <code>FallbackDefaultMatchTimeout</code> in code).
352 /// <returns>The default RegEx matching timeout for this AppDomain</returns>
353 private static TimeSpan InitDefaultMatchTimeout() {
356 AppDomain ad = AppDomain.CurrentDomain;
357 Object defTmOut = ad.GetData(DefaultMatchTimeout_ConfigKeyName);
359 // If no default is specified, use fallback:
360 if (defTmOut == null)
361 return FallbackDefaultMatchTimeout;
363 // If default has invalid type, throw. It will result in a TypeInitializationException:
364 if (!(defTmOut is TimeSpan)) {
367 String errMsg = "AppDomain.CurrentDomain.GetData(\"" + DefaultMatchTimeout_ConfigKeyName + "\")"
368 + " is expected to return null or a value of type System.TimeSpan only; but it returned a value of type"
369 + " '" + defTmOut.GetType().FullName + "'.";
370 System.Diagnostics.Debug.WriteLine(errMsg);
373 throw new InvalidCastException(SR.GetString(SR.IllegalDefaultRegexMatchTimeoutInAppDomain, DefaultMatchTimeout_ConfigKeyName));
376 // Convert default value:
377 TimeSpan defaultTimeout = (TimeSpan) defTmOut;
379 // If default timeout is outside the valid range, throw. It will result in a TypeInitializationException:
381 ValidateMatchTimeout(defaultTimeout);
383 } catch (ArgumentOutOfRangeException) {
386 String errMsg = "AppDomain.CurrentDomain.GetData(\"" + DefaultMatchTimeout_ConfigKeyName + "\")"
387 + " returned a TimeSpan value outside the valid range"
388 + " ("+ defaultTimeout.ToString() + ").";
389 System.Diagnostics.Debug.WriteLine(errMsg);
392 throw new ArgumentOutOfRangeException(SR.GetString(SR.IllegalDefaultRegexMatchTimeoutInAppDomain, DefaultMatchTimeout_ConfigKeyName));
396 return defaultTimeout;
397 } // private static TimeSpan InitDefaultMatchTimeout
398 #endif // !SILVERLIGHT
400 #if !SILVERLIGHT && !FULL_AOT_RUNTIME
402 * This method is here for perf reasons: if the call to RegexCompiler is NOT in the
403 * Regex constructor, we don't load RegexCompiler and its reflection classes when
404 * instantiating a non-compiled regex
405 * This method is internal virtual so the jit does not inline it.
409 HostProtection(MayLeakOnAbort=true),
411 MethodImplAttribute(MethodImplOptions.NoInlining)
413 internal RegexRunnerFactory Compile(RegexCode code, RegexOptions roptions) {
414 return RegexCompiler.Compile(code, roptions);
416 #endif // !SILVERLIGHT
419 * Escape metacharacters within the string
424 /// a minimal set of metacharacters (\, *, +, ?, |, {, [, (, ), ^, $, ., #, and
425 /// whitespace) by replacing them with their \ codes. This converts a string so that
426 /// it can be used as a constant within a regular expression safely. (Note that the
427 /// reason # and whitespace must be escaped is so the string can be used safely
428 /// within an expression parsed with x mode. If future Regex features add
429 /// additional metacharacters, developers should depend on Escape to escape those
430 /// characters as well.)
433 public static String Escape(String str) {
435 throw new ArgumentNullException("str");
437 return RegexParser.Escape(str);
441 * Unescape character codes within the string
445 /// Unescapes any escaped characters in the input string.
448 [SuppressMessage("Microsoft.Naming","CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId="Unescape", Justification="[....]: already shipped since v1 - can't fix without causing a breaking change")]
449 public static String Unescape(String str) {
451 throw new ArgumentNullException("str");
453 return RegexParser.Unescape(str);
456 [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "Reviewed for thread-safety")]
457 public static int CacheSize {
463 throw new ArgumentOutOfRangeException("value");
466 if (livecode.Count > cacheSize) {
468 while (livecode.Count > cacheSize)
469 livecode.RemoveLast();
476 [CLSCompliant (false)]
477 protected IDictionary Caps
481 var dict = new Dictionary<int, int>();
483 foreach (int key in caps.Keys)
485 dict.Add (key, (int)caps[key]);
493 throw new ArgumentNullException("value");
496 caps = new Hashtable (value.Count);
497 foreach (DictionaryEntry entry in value)
499 caps[(int)entry.Key] = (int)entry.Value;
504 [CLSCompliant (false)]
505 protected IDictionary CapNames
509 var dict = new Dictionary<string, int>();
511 foreach (string key in capnames.Keys)
513 dict.Add (key, (int)capnames[key]);
521 throw new ArgumentNullException("value");
523 capnames = new Hashtable (value.Count);
524 foreach (DictionaryEntry entry in value)
526 capnames[(string)entry.Key] = (int)entry.Value;
534 /// Returns the options passed into the constructor
537 public RegexOptions Options {
538 get { return roptions;}
543 /// The match timeout used by this Regex instance.
545 #if !SILVERLIGHT || FEATURE_NETCORE
550 TimeSpan MatchTimeout {
551 get { return internalMatchTimeout; }
556 * True if the regex is leftward
560 /// Indicates whether the regular expression matches from right to
564 public bool RightToLeft {
572 /// Returns the regular expression pattern passed into the constructor
575 public override string ToString() {
580 * Returns an array of the group names that are used to capture groups
581 * in the regular expression. Only needed if the regex is not known until
582 * runtime, and one wants to extract captured groups. (Probably unusual,
583 * but supplied for completeness.)
587 /// the GroupNameCollection for the regular expression. This collection contains the
588 /// set of strings used to name capturing groups in the expression.
590 public String[] GetGroupNames() {
593 if (capslist == null) {
595 result = new String[max];
597 for (int i = 0; i < max; i++) {
598 result[i] = Convert.ToString(i, CultureInfo.InvariantCulture);
602 result = new String[capslist.Length];
604 System.Array.Copy(capslist, 0, result, 0, capslist.Length);
611 * Returns an array of the group numbers that are used to capture groups
612 * in the regular expression. Only needed if the regex is not known until
613 * runtime, and one wants to extract captured groups. (Probably unusual,
614 * but supplied for completeness.)
618 /// the integer group number corresponding to a group name.
620 public int[] GetGroupNumbers() {
625 result = new int[max];
627 for (int i = 0; i < max; i++) {
632 result = new int[caps.Count];
634 IDictionaryEnumerator de = caps.GetEnumerator();
635 while (de.MoveNext()) {
636 result[(int)de.Value] = (int)de.Key;
644 * Given a group number, maps it to a group name. Note that nubmered
645 * groups automatically get a group name that is the decimal string
646 * equivalent of its number.
648 * Returns null if the number is not a recognized group number.
652 /// Retrieves a group name that corresponds to a group number.
655 public String GroupNameFromNumber(int i) {
656 if (capslist == null) {
657 if (i >= 0 && i < capsize)
658 return i.ToString(CultureInfo.InvariantCulture);
665 if (!caps.ContainsKey(i))
667 Object obj = caps[i];
679 if (i >= 0 && i < capslist.Length)
687 * Given a group name, maps it to a group number. Note that nubmered
688 * groups automatically get a group name that is the decimal string
689 * equivalent of its number.
691 * Returns -1 if the name is not a recognized group name.
695 /// Returns a group number that corresponds to a group name.
698 public int GroupNumberFromName(String name) {
702 throw new ArgumentNullException("name");
704 // look up name if we have a hashtable of names
705 if (capnames != null) {
707 if (!capnames.ContainsKey(name))
709 Object ret = capnames[name];
715 return capnames[name];
721 // convert to an int if it looks like a number
723 for (int i = 0; i < name.Length; i++) {
726 if (ch > '9' || ch < '0')
730 result += (ch - '0');
733 // return int if it's in range
734 if (result >= 0 && result < capsize)
741 * Static version of simple IsMatch call
745 /// Searches the input
746 /// string for one or more occurrences of the text supplied in the pattern
750 public static bool IsMatch(String input, String pattern) {
751 return IsMatch(input, pattern, RegexOptions.None, DefaultMatchTimeout);
755 * Static version of simple IsMatch call
759 /// Searches the input string for one or more occurrences of the text
760 /// supplied in the pattern parameter with matching options supplied in the options
764 public static bool IsMatch(String input, String pattern, RegexOptions options) {
765 return IsMatch(input, pattern, options, DefaultMatchTimeout);
768 #if !SILVERLIGHT || FEATURE_NETCORE
773 static bool IsMatch(String input, String pattern, RegexOptions options, TimeSpan matchTimeout) {
774 return new Regex(pattern, options, matchTimeout, true).IsMatch(input);
778 * Returns true if the regex finds a match within the specified string
782 /// Searches the input string for one or
783 /// more matches using the previous pattern, options, and starting
787 public bool IsMatch(String input) {
790 throw new ArgumentNullException("input");
792 return IsMatch(input, UseOptionR() ? input.Length : 0);
796 * Returns true if the regex finds a match after the specified position
797 * (proceeding leftward if the regex is leftward and rightward otherwise)
801 /// Searches the input
802 /// string for one or more matches using the previous pattern and options, with
803 /// a new starting position.
806 public bool IsMatch(String input, int startat) {
809 throw new ArgumentNullException("input");
811 return (null == Run(true, -1, input, 0, input.Length, startat));
815 * Static version of simple Match call
819 /// Searches the input string for one or more occurrences of the text
820 /// supplied in the pattern parameter.
823 public static Match Match(String input, String pattern) {
824 return Match(input, pattern, RegexOptions.None, DefaultMatchTimeout);
828 * Static version of simple Match call
832 /// Searches the input string for one or more occurrences of the text
833 /// supplied in the pattern parameter. Matching is modified with an option
837 public static Match Match(String input, String pattern, RegexOptions options) {
838 return Match(input, pattern, options, DefaultMatchTimeout);
842 #if !SILVERLIGHT || FEATURE_NETCORE
847 static Match Match(String input, String pattern, RegexOptions options, TimeSpan matchTimeout) {
848 return new Regex(pattern, options, matchTimeout, true).Match(input);
852 * Finds the first match for the regular expression starting at the beginning
853 * of the string (or at the end of the string if the regex is leftward)
857 /// Matches a regular expression with a string and returns
858 /// the precise result as a RegexMatch object.
861 public Match Match(String input) {
864 throw new ArgumentNullException("input");
866 return Match(input, UseOptionR() ? input.Length : 0);
870 * Finds the first match, starting at the specified position
873 /// Matches a regular expression with a string and returns
874 /// the precise result as a RegexMatch object.
876 public Match Match(String input, int startat) {
879 throw new ArgumentNullException("input");
881 return Run(false, -1, input, 0, input.Length, startat);
885 * Finds the first match, restricting the search to the specified interval of
891 /// regular expression with a string and returns the precise result as a
892 /// RegexMatch object.
895 public Match Match(String input, int beginning, int length) {
897 throw new ArgumentNullException("input");
899 return Run(false, -1, input, beginning, length, UseOptionR() ? beginning + length : beginning);
903 * Static version of simple Matches call
907 /// Returns all the successful matches as if Match were
908 /// called iteratively numerous times.
911 public static MatchCollection Matches(String input, String pattern) {
912 return Matches(input, pattern, RegexOptions.None, DefaultMatchTimeout);
916 * Static version of simple Matches call
920 /// Returns all the successful matches as if Match were called iteratively
924 public static MatchCollection Matches(String input, String pattern, RegexOptions options) {
925 return Matches(input, pattern, options, DefaultMatchTimeout);
928 #if !SILVERLIGHT || FEATURE_NETCORE
933 static MatchCollection Matches(String input, String pattern, RegexOptions options, TimeSpan matchTimeout) {
934 return new Regex(pattern, options, matchTimeout, true).Matches(input);
938 * Finds the first match for the regular expression starting at the beginning
939 * of the string Enumerator(or at the end of the string if the regex is leftward)
944 /// all the successful matches as if Match was called iteratively numerous
948 public MatchCollection Matches(String input) {
951 throw new ArgumentNullException("input");
953 return Matches(input, UseOptionR() ? input.Length : 0);
957 * Finds the first match, starting at the specified position
962 /// all the successful matches as if Match was called iteratively numerous
966 public MatchCollection Matches(String input, int startat) {
969 throw new ArgumentNullException("input");
971 return new MatchCollection(this, input, 0, input.Length, startat);
975 * Static version of simple Replace call
980 /// all occurrences of the pattern with the <paramref name="replacement"/> pattern, starting at
981 /// the first character in the input string.
984 public static String Replace(String input, String pattern, String replacement) {
985 return Replace(input, pattern, replacement, RegexOptions.None, DefaultMatchTimeout);
989 * Static version of simple Replace call
993 /// Replaces all occurrences of
994 /// the <paramref name="pattern "/>with the <paramref name="replacement "/>
995 /// pattern, starting at the first character in the input string.
998 public static String Replace(String input, String pattern, String replacement, RegexOptions options) {
999 return Replace(input, pattern, replacement, options, DefaultMatchTimeout);
1002 #if !SILVERLIGHT || FEATURE_NETCORE
1007 static String Replace(String input, String pattern, String replacement, RegexOptions options, TimeSpan matchTimeout) {
1008 return new Regex(pattern, options, matchTimeout, true).Replace(input, replacement);
1012 * Does the replacement
1016 /// Replaces all occurrences of
1017 /// the <paramref name="pattern "/> with the <paramref name="replacement"/> pattern, starting at the
1018 /// first character in the input string, using the previous patten.
1021 public String Replace(String input, String replacement) {
1024 throw new ArgumentNullException("input");
1026 return Replace(input, replacement, -1, UseOptionR() ? input.Length : 0);
1030 * Does the replacement
1034 /// Replaces all occurrences of the (previously defined) <paramref name="pattern "/>with the
1035 /// <paramref name="replacement"/> pattern, starting at the first character in the input string.
1038 public String Replace(String input, String replacement, int count) {
1041 throw new ArgumentNullException("input");
1043 return Replace(input, replacement, count, UseOptionR() ? input.Length : 0);
1047 * Does the replacement
1051 /// Replaces all occurrences of the <paramref name="pattern "/>with the recent
1052 /// <paramref name="replacement"/> pattern, starting at the character position
1053 /// <paramref name="startat."/>
1056 public String Replace(String input, String replacement, int count, int startat) {
1059 throw new ArgumentNullException("input");
1061 if (replacement == null)
1062 throw new ArgumentNullException("replacement");
1064 // a little code to grab a cached parsed replacement object
1065 RegexReplacement repl = (RegexReplacement) replref.Get();
1067 if (repl == null || !repl.Pattern.Equals(replacement)) {
1068 repl = RegexParser.ParseReplacement(replacement, caps, capsize, capnames, this.roptions);
1069 replref.Cache(repl);
1072 return repl.Replace(this, input, count, startat);
1076 * Static version of simple Replace call
1080 /// Replaces all occurrences of the <paramref name="pattern "/>with the
1081 /// <paramref name="replacement"/> pattern
1082 /// <paramref name="."/>
1085 public static String Replace(String input, String pattern, MatchEvaluator evaluator) {
1086 return Replace(input, pattern, evaluator, RegexOptions.None, DefaultMatchTimeout);
1090 * Static version of simple Replace call
1094 /// Replaces all occurrences of the <paramref name="pattern "/>with the recent
1095 /// <paramref name="replacement"/> pattern, starting at the first character<paramref name="."/>
1098 public static String Replace(String input, String pattern, MatchEvaluator evaluator, RegexOptions options) {
1099 return Replace(input, pattern, evaluator, options, DefaultMatchTimeout);
1102 #if !SILVERLIGHT || FEATURE_NETCORE
1107 static String Replace(String input, String pattern, MatchEvaluator evaluator, RegexOptions options, TimeSpan matchTimeout) {
1108 return new Regex(pattern, options, matchTimeout, true).Replace(input, evaluator);
1112 * Does the replacement
1116 /// Replaces all occurrences of the <paramref name="pattern "/>with the recent
1117 /// <paramref name="replacement"/> pattern, starting at the first character
1118 /// position<paramref name="."/>
1121 public String Replace(String input, MatchEvaluator evaluator) {
1124 throw new ArgumentNullException("input");
1126 return Replace(input, evaluator, -1, UseOptionR() ? input.Length : 0);
1130 * Does the replacement
1134 /// Replaces all occurrences of the <paramref name="pattern "/>with the recent
1135 /// <paramref name="replacement"/> pattern, starting at the first character
1136 /// position<paramref name="."/>
1139 public String Replace(String input, MatchEvaluator evaluator, int count) {
1142 throw new ArgumentNullException("input");
1144 return Replace(input, evaluator, count, UseOptionR() ? input.Length : 0);
1148 * Does the replacement
1152 /// Replaces all occurrences of the (previouly defined) <paramref name="pattern "/>with
1153 /// the recent <paramref name="replacement"/> pattern, starting at the character
1154 /// position<paramref name=" startat."/>
1157 public String Replace(String input, MatchEvaluator evaluator, int count, int startat) {
1160 throw new ArgumentNullException("input");
1162 return RegexReplacement.Replace(evaluator, this, input, count, startat);
1166 * Static version of simple Split call
1170 /// Splits the <paramref name="input "/>string at the position defined
1171 /// by <paramref name="pattern"/>.
1174 public static String[] Split(String input, String pattern) {
1175 return Split(input, pattern, RegexOptions.None, DefaultMatchTimeout);
1179 * Static version of simple Split call
1183 /// Splits the <paramref name="input "/>string at the position defined by <paramref name="pattern"/>.
1186 public static String[] Split(String input, String pattern, RegexOptions options) {
1187 return Split(input, pattern, options, DefaultMatchTimeout);
1190 #if !SILVERLIGHT || FEATURE_NETCORE
1195 static String[] Split(String input, String pattern, RegexOptions options, TimeSpan matchTimeout) {
1196 return new Regex(pattern, options, matchTimeout, true).Split(input);
1204 /// Splits the <paramref name="input "/>string at the position defined by
1205 /// a previous <paramref name="pattern"/>
1209 public String[] Split(String input) {
1212 throw new ArgumentNullException("input");
1214 return Split(input, 0, UseOptionR() ? input.Length : 0);
1222 /// Splits the <paramref name="input "/>string at the position defined by a previous
1223 /// <paramref name="pattern"/> .
1226 public String[] Split(String input, int count) {
1229 throw new ArgumentNullException("input");
1231 return RegexReplacement.Split(this, input, count, UseOptionR() ? input.Length : 0);
1239 /// Splits the <paramref name="input "/>string at the position defined by a previous
1240 /// <paramref name="pattern"/> .
1243 public String[] Split(String input, int count, int startat) {
1245 throw new ArgumentNullException("input");
1247 return RegexReplacement.Split(this, input, count, startat);
1252 #if !(SILVERLIGHT || FULL_AOT_RUNTIME)
1255 #if MONO_FEATURE_CAS
1256 [HostProtection(MayLeakOnAbort=true)]
1258 [ResourceExposure(ResourceScope.Machine)] // The AssemblyName is interesting.
1259 [ResourceConsumption(ResourceScope.Machine)]
1260 [SuppressMessage("Microsoft.Naming","CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId="assemblyname", Justification="[....]: already shipped since v1 - can't fix without causing a breaking change")]
1261 public static void CompileToAssembly(RegexCompilationInfo[] regexinfos, AssemblyName assemblyname) {
1263 CompileToAssemblyInternal(regexinfos, assemblyname, null, null);
1268 #if MONO_FEATURE_CAS
1269 [HostProtection(MayLeakOnAbort=true)]
1271 [ResourceExposure(ResourceScope.Machine)] // The AssemblyName is interesting.
1272 [ResourceConsumption(ResourceScope.Machine)]
1273 [SuppressMessage("Microsoft.Naming","CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId="assemblyname", Justification="[....]: already shipped since v1 - can't fix without causing a breaking change")]
1274 public static void CompileToAssembly(RegexCompilationInfo[] regexinfos, AssemblyName assemblyname, CustomAttributeBuilder[] attributes) {
1275 CompileToAssemblyInternal(regexinfos, assemblyname, attributes, null);
1278 #if MONO_FEATURE_CAS
1279 [HostProtection(MayLeakOnAbort=true)]
1281 [ResourceExposure(ResourceScope.Machine)]
1282 [ResourceConsumption(ResourceScope.Machine)]
1283 [SuppressMessage("Microsoft.Naming","CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId="assemblyname", Justification="[....]: already shipped since v1 - can't fix without causing a breaking change")]
1284 public static void CompileToAssembly(RegexCompilationInfo[] regexinfos, AssemblyName assemblyname, CustomAttributeBuilder[] attributes, String resourceFile) {
1285 CompileToAssemblyInternal(regexinfos, assemblyname, attributes, resourceFile);
1288 [ResourceExposure(ResourceScope.Machine)] // AssemblyName & resourceFile
1289 [ResourceConsumption(ResourceScope.Machine)]
1290 private static void CompileToAssemblyInternal (RegexCompilationInfo[] regexinfos, AssemblyName assemblyname, CustomAttributeBuilder[] attributes, String resourceFile) {
1291 if (assemblyname == null)
1292 throw new ArgumentNullException("assemblyname");
1294 if (regexinfos == null)
1295 throw new ArgumentNullException("regexinfos");
1297 RegexCompiler.CompileToAssembly(regexinfos, assemblyname, attributes, resourceFile);
1304 protected void InitializeReferences() {
1305 if (refsInitialized)
1306 throw new NotSupportedException(SR.GetString(SR.OnlyAllowedOnce));
1308 refsInitialized = true;
1309 runnerref = new ExclusiveReference();
1310 replref = new SharedReference();
1315 * Internal worker called by all the public APIs
1317 internal Match Run(bool quick, int prevlen, String input, int beginning, int length, int startat) {
1319 RegexRunner runner = null;
1321 if (startat < 0 || startat > input.Length)
1322 throw new ArgumentOutOfRangeException("start", SR.GetString(SR.BeginIndexNotNegative));
1324 if (length < 0 || length > input.Length)
1325 throw new ArgumentOutOfRangeException("length", SR.GetString(SR.LengthNotNegative));
1327 // There may be a cached runner; grab ownership of it if we can.
1329 runner = (RegexRunner)runnerref.Get();
1331 // Create a RegexRunner instance if we need to
1333 if (runner == null) {
1334 // Use the compiled RegexRunner factory if the code was compiled to MSIL
1336 if (factory != null)
1337 runner = factory.CreateInstance();
1339 runner = new RegexInterpreter(code, UseOptionInvariant() ? CultureInfo.InvariantCulture : CultureInfo.CurrentCulture);
1343 // Do the scan starting at the requested position
1344 match = runner.Scan(this, input, beginning, beginning + length, startat, prevlen, quick, internalMatchTimeout);
1346 // Release or fill the cache slot
1347 runnerref.Release(runner);
1351 if (Debug && match != null)
1358 * Find code cache based on options+pattern
1360 private static CachedCodeEntry LookupCachedAndUpdate(String key) {
1362 for (LinkedListNode<CachedCodeEntry> current = livecode.First; current != null; current = current.Next) {
1363 if (current.Value._key == key) {
1364 // If we find an entry in the cache, move it to the head at the same time.
1365 livecode.Remove(current);
1366 livecode.AddFirst(current);
1367 return current.Value;
1376 * Add current code to the cache
1378 private CachedCodeEntry CacheCode(String key) {
1379 CachedCodeEntry newcached = null;
1382 // first look for it in the cache and move it to the head
1383 for (LinkedListNode<CachedCodeEntry> current = livecode.First; current != null; current = current.Next) {
1384 if (current.Value._key == key) {
1385 livecode.Remove(current);
1386 livecode.AddFirst(current);
1387 return current.Value;
1391 // it wasn't in the cache, so we'll add a new one. Shortcut out for the case where cacheSize is zero.
1392 if (cacheSize != 0) {
1393 newcached = new CachedCodeEntry(key, capnames, capslist, code, caps, capsize, runnerref, replref);
1394 livecode.AddFirst(newcached);
1395 if (livecode.Count > cacheSize)
1396 livecode.RemoveLast();
1405 * True if the O option was set
1410 protected bool UseOptionC() {
1411 #if FULL_AOT_RUNTIME
1416 /* Mono: Set to false until we investigate https://bugzilla.xamarin.com/show_bug.cgi?id=25671 */
1419 return(roptions & RegexOptions.Compiled) != 0;
1426 * True if the L option was set
1431 protected bool UseOptionR() {
1432 return(roptions & RegexOptions.RightToLeft) != 0;
1435 internal bool UseOptionInvariant() {
1436 return(roptions & RegexOptions.CultureInvariant) != 0;
1442 * True if the regex has debugging enabled
1447 internal bool Debug {
1449 return(roptions & RegexOptions.Debug) != 0;
1465 public delegate String MatchEvaluator(Match match);
1469 * Used to cache byte codes or compiled factories
1471 internal sealed class CachedCodeEntry {
1472 internal string _key;
1473 internal RegexCode _code;
1475 internal Dictionary<Int32, Int32> _caps;
1476 internal Dictionary<String, Int32> _capnames;
1478 internal Hashtable _caps;
1479 internal Hashtable _capnames;
1481 internal String[] _capslist;
1482 internal int _capsize;
1483 internal RegexRunnerFactory _factory;
1484 internal ExclusiveReference _runnerref;
1485 internal SharedReference _replref;
1488 internal CachedCodeEntry(string key, Dictionary<String, Int32> capnames, String[] capslist, RegexCode code, Dictionary<Int32, Int32> caps, int capsize, ExclusiveReference runner, SharedReference repl)
1490 internal CachedCodeEntry(string key, Hashtable capnames, String[] capslist, RegexCode code, Hashtable caps, int capsize, ExclusiveReference runner, SharedReference repl)
1495 _capnames = capnames;
1496 _capslist = capslist;
1502 _runnerref = runner;
1507 internal void AddCompiled(RegexRunnerFactory factory) {
1515 * Used to cache one exclusive runner reference
1517 internal sealed class ExclusiveReference {
1523 * Return an object and grab an exclusive lock.
1525 * If the exclusive lock can't be obtained, null is returned;
1526 * if the object can't be returned, the lock is released.
1529 internal Object Get() {
1530 // try to obtain the lock
1532 if (0 == Interlocked.Exchange(ref _locked, 1)) {
1538 // release the lock and return null if no reference
1545 // remember the reference and keep the lock
1555 * Release an object back to the cache
1557 * If the object is the one that's under lock, the lock
1560 * If there is no cached object, then the lock is obtained
1561 * and the object is placed in the cache.
1564 internal void Release(Object obj) {
1566 throw new ArgumentNullException("obj");
1568 // if this reference owns the lock, release it
1576 // if no reference owns the lock, try to cache this reference
1579 // try to obtain the lock
1581 if (0 == Interlocked.Exchange(ref _locked, 1)) {
1582 // if there's really no reference, cache this reference
1585 _ref = (RegexRunner) obj;
1597 * Used to cache a weak reference in a threadsafe way
1599 internal sealed class SharedReference {
1600 WeakReference _ref = new WeakReference(null);
1604 * Return an object from a weakref, protected by a lock.
1606 * If the exclusive lock can't be obtained, null is returned;
1608 * Note that _ref.Target is referenced only under the protection
1609 * of the lock. (Is this necessary?)
1611 internal Object Get() {
1612 if (0 == Interlocked.Exchange(ref _locked, 1)) {
1613 Object obj = _ref.Target;
1622 * Suggest an object into a weakref, protected by a lock.
1624 * Note that _ref.Target is referenced only under the protection
1625 * of the lock. (Is this necessary?)
1627 internal void Cache(Object obj) {
1628 if (0 == Interlocked.Exchange(ref _locked, 1)) {