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;
16 using System.Reflection.Emit;
17 using System.Globalization;
18 using System.Security.Permissions;
19 using System.Runtime.CompilerServices;
20 using System.Collections.Generic;
21 using System.Diagnostics.CodeAnalysis;
24 using System.Runtime.Serialization;
25 using System.Runtime.Versioning;
31 /// Represents an immutable, compiled regular expression. Also
32 /// contains static methods that allow use of regular expressions without instantiating
33 /// a Regex explicitly.
45 // Fields used by precompiled regexes
46 protected internal string pattern;
48 protected internal RegexRunnerFactory factory; // if compiled, this is the RegexRunner subclass
50 internal RegexRunnerFactory factory; // if compiled, this is the RegexRunner subclass
53 protected internal RegexOptions roptions; // the top-level options from the options string
56 // *********** Match timeout fields { ***********
58 // We need this because time is queried using Environment.TickCount for performance reasons
59 // (Environment.TickCount returns millisecs as an int and cycles):
63 private static readonly TimeSpan MaximumMatchTimeout = TimeSpan.FromMilliseconds(Int32.MaxValue - 1);
65 // InfiniteMatchTimeout specifies that match timeout is switched OFF. It allows for faster code paths
66 // compared to simply having a very large timeout.
67 // We do not want to ask users to use System.Threading.Timeout.InfiniteTimeSpan as a parameter because:
68 // (1) We do not want to imply any relation between having using a RegEx timeout and using multi-threading.
69 // (2) We do not want to require users to take ref to a contract assembly for threading just to use RegEx.
70 // There may in theory be a SKU that has RegEx, but no multithreading.
71 // We create a public Regex.InfiniteMatchTimeout constant, which for consistency uses the save underlying
72 // value as Timeout.InfiniteTimeSpan creating an implementation detail dependency only.
73 #if !SILVERLIGHT || FEATURE_NETCORE
77 public static readonly TimeSpan InfiniteMatchTimeout =
79 new TimeSpan (0, 0, 0, 0, Timeout.Infinite);
81 Timeout.InfiniteTimeSpan;
84 internal static readonly TimeSpan InfiniteMatchTimeout = new TimeSpan(0, 0, 0, 0, Timeout.Infinite);
87 // All these protected internal fields in this class really should not be protected. The historic reason
88 // for this is that classes extending Regex that are generated via CompileToAssembly rely on the fact that
89 // these are accessible as protected in order to initialise them in the generated constructor of the
90 // extending class. We should update this initialisation logic to using a protected constructor, but until
91 // that is done we stick to the existing pattern however ugly it may be.
93 [OptionalField(VersionAdded = 2)]
98 TimeSpan internalMatchTimeout; // timeout for the execution of this regex
101 // During static initialisation of Regex we check
102 private const String DefaultMatchTimeout_ConfigKeyName = "REGEX_DEFAULT_MATCH_TIMEOUT";
105 // FallbackDefaultMatchTimeout specifies the match timeout to use if no other timeout was specified
106 // by one means or another. For now it is set to InfiniteMatchTimeout, meaning timeouts are OFF by
107 // default (for Dev12 we plan to set a positive value).
108 // Having this field is helpful to read the code as it makes it clear when we mean
109 // "default that is currently no-timeouts" and when we mean "actually no-timeouts".
110 // In Silverlight, DefaultMatchTimeout is always set to FallbackDefaultMatchTimeout,
111 // on desktop, DefaultMatchTimeout can be configured via AppDomain and falls back to
112 // FallbackDefaultMatchTimeout, if no AppDomain setting is present (see InitDefaultMatchTimeout()).
116 internal static readonly TimeSpan FallbackDefaultMatchTimeout = InfiniteMatchTimeout;
119 // DefaultMatchTimeout specifies the match timeout to use if no other timeout was specified
120 // by one means or another. Typically, it is set to InfiniteMatchTimeout in Dev 11
121 // (we plan to set a positive timeout in Dev12).
122 // Hosts (e.g.) ASP may set an AppDomain property via SetData to change the default value.
125 internal static readonly TimeSpan DefaultMatchTimeout = InitDefaultMatchTimeout();
127 internal static readonly TimeSpan DefaultMatchTimeout = FallbackDefaultMatchTimeout;
130 // *********** } match timeout fields ***********
134 internal Dictionary<Int32, Int32> caps; // if captures are sparse, this is the hashtable capnum->index
135 internal Dictionary<String, Int32> capnames; // if named captures are used, this maps names->index
137 // desktop build still uses non-generic collections for AppCompat with .NET Framework 3.5 pre-compiled assemblies
138 protected internal Hashtable caps;
139 protected internal Hashtable capnames;
141 protected internal String[] capslist; // if captures are sparse or named captures are used, this is the sorted list of names
142 protected internal int capsize; // the size of the capture array
144 internal ExclusiveReference runnerref; // cached runner
145 internal SharedReference replref; // cached parsed replacement pattern
146 internal RegexCode code; // if interpreted, this is the code for RegexIntepreter
147 internal bool refsInitialized = false;
149 internal static LinkedList<CachedCodeEntry> livecode = new LinkedList<CachedCodeEntry>();// the cached of code and factories that are currently loaded
150 internal static int cacheSize = 15;
152 internal const int MaxOptionShift = 10;
156 // If a compiled-to-assembly RegEx was generated using an earlier version, then internalMatchTimeout will be uninitialised.
158 // In distant future, when RegEx generated using pre Dev11 are not supported any more, we can remove this to aid performance:
160 this.internalMatchTimeout = DefaultMatchTimeout;
164 * Compiles and returns a Regex object corresponding to the given pattern
168 /// Creates and compiles a regular expression object for the specified regular
172 public Regex(String pattern)
173 : this(pattern, RegexOptions.None, DefaultMatchTimeout, false) {
177 * Returns a Regex object corresponding to the given pattern, compiled with
178 * the specified options.
182 /// Creates and compiles a regular expression object for the
183 /// specified regular expression
184 /// with options that modify the pattern.
187 public Regex(String pattern, RegexOptions options)
188 : this(pattern, options, DefaultMatchTimeout, false) {
191 #if !SILVERLIGHT || FEATURE_NETCORE
196 Regex(String pattern, RegexOptions options, TimeSpan matchTimeout)
197 : this(pattern, options, matchTimeout, false) {
200 private Regex(String pattern, RegexOptions options, TimeSpan matchTimeout, bool useCache) {
202 CachedCodeEntry cached = null;
203 string cultureKey = null;
206 throw new ArgumentNullException("pattern");
207 if (options < RegexOptions.None || ( ((int) options) >> MaxOptionShift) != 0)
208 throw new ArgumentOutOfRangeException("options");
209 if ((options & RegexOptions.ECMAScript) != 0
210 && (options & ~(RegexOptions.ECMAScript |
211 RegexOptions.IgnoreCase |
212 RegexOptions.Multiline |
213 #if !SILVERLIGHT || FEATURE_LEGACYNETCF
214 RegexOptions.Compiled |
216 RegexOptions.CultureInvariant
221 throw new ArgumentOutOfRangeException("options");
223 ValidateMatchTimeout(matchTimeout);
225 // Try to look up this regex in the cache. We do this regardless of whether useCache is true since there's
226 // really no reason not to.
227 if ((options & RegexOptions.CultureInvariant) != 0)
228 cultureKey = CultureInfo.InvariantCulture.ToString(); // "English (United States)"
230 cultureKey = CultureInfo.CurrentCulture.ToString();
232 String key = ((int) options).ToString(NumberFormatInfo.InvariantInfo) + ":" + cultureKey + ":" + pattern;
233 cached = LookupCachedAndUpdate(key);
235 this.pattern = pattern;
236 this.roptions = options;
238 this.internalMatchTimeout = matchTimeout;
240 if (cached == null) {
242 tree = RegexParser.Parse(pattern, roptions);
244 // Extract the relevant information
245 capnames = tree._capnames;
246 capslist = tree._capslist;
247 code = RegexWriter.Write(tree);
249 capsize = code._capsize;
251 InitializeReferences();
255 cached = CacheCode(key);
259 capnames = cached._capnames;
260 capslist = cached._capslist;
261 capsize = cached._capsize;
263 factory = cached._factory;
264 runnerref = cached._runnerref;
265 replref = cached._replref;
266 refsInitialized = true;
270 // if the compile option is set, then compile the code if it's not already
271 if (UseOptionC() && factory == null) {
272 factory = Compile(code, roptions);
274 if (useCache && cached != null)
275 cached.AddCompiled(factory);
283 * ISerializable constructor
285 protected Regex(SerializationInfo info, StreamingContext context)
286 : this(info.GetString("pattern"), (RegexOptions) info.GetInt32("options")) {
289 Int64 timeoutTicks = info.GetInt64("matchTimeout");
290 TimeSpan timeout = new TimeSpan(timeoutTicks);
291 ValidateMatchTimeout(timeout);
292 this.internalMatchTimeout = timeout;
293 } catch (SerializationException) {
294 // If this occurs, then assume that this object was serialised using a version
295 // before timeout was added. In that case just do not set a timeout
296 // (keep default value)
302 * ISerializable method
305 void ISerializable.GetObjectData(SerializationInfo si, StreamingContext context) {
306 si.AddValue("pattern", this.ToString());
307 si.AddValue("options", this.Options);
308 si.AddValue("matchTimeout", this.MatchTimeout.Ticks);
310 #endif // !SILVERLIGHT
312 //* Note: "<" is the XML entity for smaller ("<").
314 /// Validates that the specified match timeout value is valid.
315 /// The valid range is <code>TimeSpan.Zero < matchTimeout <= Regex.MaximumMatchTimeout</code>.
317 /// <param name="matchTimeout">The timeout value to validate.</param>
318 /// <exception cref="System.ArgumentOutOfRangeException">If the specified timeout is not within a valid range.
325 static void ValidateMatchTimeout(TimeSpan matchTimeout) {
327 if (InfiniteMatchTimeout == matchTimeout)
330 // Change this to make sure timeout is not longer then Environment.Ticks cycle length:
331 if (TimeSpan.Zero < matchTimeout && matchTimeout <= MaximumMatchTimeout)
334 throw new ArgumentOutOfRangeException("matchTimeout");
339 /// Specifies the default RegEx matching timeout value (i.e. the timeout that will be used if no
340 /// explicit timeout is specified).
341 /// The default is queried from the current <code>AppDomain</code> through <code>GetData</code> using
342 /// the key specified in <code>Regex.DefaultMatchTimeout_ConfigKeyName</code>. For that key, the
343 /// current <code>AppDomain</code> is expected to either return <code>null</code> or a <code>TimeSpan</code>
344 /// value specifying the default timeout within a valid range.
345 /// If the AddDomain's data value for that key is not a <code>TimeSpan</code> value or if it is outside the
346 /// valid range, an exception is thrown which will result in a <code>TypeInitializationException</code> for RegEx.
347 /// If the AddDomain's data value for that key is <code>null</code>, a fallback value is returned
348 /// (see <code>FallbackDefaultMatchTimeout</code> in code).
350 /// <returns>The default RegEx matching timeout for this AppDomain</returns>
351 private static TimeSpan InitDefaultMatchTimeout() {
354 AppDomain ad = AppDomain.CurrentDomain;
355 Object defTmOut = ad.GetData(DefaultMatchTimeout_ConfigKeyName);
357 // If no default is specified, use fallback:
358 if (defTmOut == null)
359 return FallbackDefaultMatchTimeout;
361 // If default has invalid type, throw. It will result in a TypeInitializationException:
362 if (!(defTmOut is TimeSpan)) {
365 String errMsg = "AppDomain.CurrentDomain.GetData(\"" + DefaultMatchTimeout_ConfigKeyName + "\")"
366 + " is expected to return null or a value of type System.TimeSpan only; but it returned a value of type"
367 + " '" + defTmOut.GetType().FullName + "'.";
368 System.Diagnostics.Debug.WriteLine(errMsg);
371 throw new InvalidCastException(SR.GetString(SR.IllegalDefaultRegexMatchTimeoutInAppDomain, DefaultMatchTimeout_ConfigKeyName));
374 // Convert default value:
375 TimeSpan defaultTimeout = (TimeSpan) defTmOut;
377 // If default timeout is outside the valid range, throw. It will result in a TypeInitializationException:
379 ValidateMatchTimeout(defaultTimeout);
381 } catch (ArgumentOutOfRangeException) {
384 String errMsg = "AppDomain.CurrentDomain.GetData(\"" + DefaultMatchTimeout_ConfigKeyName + "\")"
385 + " returned a TimeSpan value outside the valid range"
386 + " ("+ defaultTimeout.ToString() + ").";
387 System.Diagnostics.Debug.WriteLine(errMsg);
390 throw new ArgumentOutOfRangeException(SR.GetString(SR.IllegalDefaultRegexMatchTimeoutInAppDomain, DefaultMatchTimeout_ConfigKeyName));
394 return defaultTimeout;
395 } // private static TimeSpan InitDefaultMatchTimeout
396 #endif // !SILVERLIGHT
400 * This method is here for perf reasons: if the call to RegexCompiler is NOT in the
401 * Regex constructor, we don't load RegexCompiler and its reflection classes when
402 * instantiating a non-compiled regex
403 * This method is internal virtual so the jit does not inline it.
407 HostProtection(MayLeakOnAbort=true),
409 MethodImplAttribute(MethodImplOptions.NoInlining)
411 internal RegexRunnerFactory Compile(RegexCode code, RegexOptions roptions) {
412 return RegexCompiler.Compile(code, roptions);
414 #endif // !SILVERLIGHT
417 * Escape metacharacters within the string
422 /// a minimal set of metacharacters (\, *, +, ?, |, {, [, (, ), ^, $, ., #, and
423 /// whitespace) by replacing them with their \ codes. This converts a string so that
424 /// it can be used as a constant within a regular expression safely. (Note that the
425 /// reason # and whitespace must be escaped is so the string can be used safely
426 /// within an expression parsed with x mode. If future Regex features add
427 /// additional metacharacters, developers should depend on Escape to escape those
428 /// characters as well.)
431 public static String Escape(String str) {
433 throw new ArgumentNullException("str");
435 return RegexParser.Escape(str);
439 * Unescape character codes within the string
443 /// Unescapes any escaped characters in the input string.
446 [SuppressMessage("Microsoft.Naming","CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId="Unescape", Justification="[....]: already shipped since v1 - can't fix without causing a breaking change")]
447 public static String Unescape(String str) {
449 throw new ArgumentNullException("str");
451 return RegexParser.Unescape(str);
454 [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "Reviewed for thread-safety")]
455 public static int CacheSize {
461 throw new ArgumentOutOfRangeException("value");
464 if (livecode.Count > cacheSize) {
466 while (livecode.Count > cacheSize)
467 livecode.RemoveLast();
475 /// Returns the options passed into the constructor
478 public RegexOptions Options {
479 get { return roptions;}
484 /// The match timeout used by this Regex instance.
486 #if !SILVERLIGHT || FEATURE_NETCORE
491 TimeSpan MatchTimeout {
492 get { return internalMatchTimeout; }
497 * True if the regex is leftward
501 /// Indicates whether the regular expression matches from right to
505 public bool RightToLeft {
513 /// Returns the regular expression pattern passed into the constructor
516 public override string ToString() {
521 * Returns an array of the group names that are used to capture groups
522 * in the regular expression. Only needed if the regex is not known until
523 * runtime, and one wants to extract captured groups. (Probably unusual,
524 * but supplied for completeness.)
528 /// the GroupNameCollection for the regular expression. This collection contains the
529 /// set of strings used to name capturing groups in the expression.
531 public String[] GetGroupNames() {
534 if (capslist == null) {
536 result = new String[max];
538 for (int i = 0; i < max; i++) {
539 result[i] = Convert.ToString(i, CultureInfo.InvariantCulture);
543 result = new String[capslist.Length];
545 System.Array.Copy(capslist, 0, result, 0, capslist.Length);
552 * Returns an array of the group numbers that are used to capture groups
553 * in the regular expression. Only needed if the regex is not known until
554 * runtime, and one wants to extract captured groups. (Probably unusual,
555 * but supplied for completeness.)
559 /// the integer group number corresponding to a group name.
561 public int[] GetGroupNumbers() {
566 result = new int[max];
568 for (int i = 0; i < max; i++) {
573 result = new int[caps.Count];
575 IDictionaryEnumerator de = caps.GetEnumerator();
576 while (de.MoveNext()) {
577 result[(int)de.Value] = (int)de.Key;
585 * Given a group number, maps it to a group name. Note that nubmered
586 * groups automatically get a group name that is the decimal string
587 * equivalent of its number.
589 * Returns null if the number is not a recognized group number.
593 /// Retrieves a group name that corresponds to a group number.
596 public String GroupNameFromNumber(int i) {
597 if (capslist == null) {
598 if (i >= 0 && i < capsize)
599 return i.ToString(CultureInfo.InvariantCulture);
606 if (!caps.ContainsKey(i))
608 Object obj = caps[i];
620 if (i >= 0 && i < capslist.Length)
628 * Given a group name, maps it to a group number. Note that nubmered
629 * groups automatically get a group name that is the decimal string
630 * equivalent of its number.
632 * Returns -1 if the name is not a recognized group name.
636 /// Returns a group number that corresponds to a group name.
639 public int GroupNumberFromName(String name) {
643 throw new ArgumentNullException("name");
645 // look up name if we have a hashtable of names
646 if (capnames != null) {
648 if (!capnames.ContainsKey(name))
650 Object ret = capnames[name];
656 return capnames[name];
662 // convert to an int if it looks like a number
664 for (int i = 0; i < name.Length; i++) {
667 if (ch > '9' || ch < '0')
671 result += (ch - '0');
674 // return int if it's in range
675 if (result >= 0 && result < capsize)
682 * Static version of simple IsMatch call
686 /// Searches the input
687 /// string for one or more occurrences of the text supplied in the pattern
691 public static bool IsMatch(String input, String pattern) {
692 return IsMatch(input, pattern, RegexOptions.None, DefaultMatchTimeout);
696 * Static version of simple IsMatch call
700 /// Searches the input string for one or more occurrences of the text
701 /// supplied in the pattern parameter with matching options supplied in the options
705 public static bool IsMatch(String input, String pattern, RegexOptions options) {
706 return IsMatch(input, pattern, options, DefaultMatchTimeout);
709 #if !SILVERLIGHT || FEATURE_NETCORE
714 static bool IsMatch(String input, String pattern, RegexOptions options, TimeSpan matchTimeout) {
715 return new Regex(pattern, options, matchTimeout, true).IsMatch(input);
719 * Returns true if the regex finds a match within the specified string
723 /// Searches the input string for one or
724 /// more matches using the previous pattern, options, and starting
728 public bool IsMatch(String input) {
731 throw new ArgumentNullException("input");
733 return IsMatch(input, UseOptionR() ? input.Length : 0);
737 * Returns true if the regex finds a match after the specified position
738 * (proceeding leftward if the regex is leftward and rightward otherwise)
742 /// Searches the input
743 /// string for one or more matches using the previous pattern and options, with
744 /// a new starting position.
747 public bool IsMatch(String input, int startat) {
750 throw new ArgumentNullException("input");
752 return (null == Run(true, -1, input, 0, input.Length, startat));
756 * Static version of simple Match call
760 /// Searches the input string for one or more occurrences of the text
761 /// supplied in the pattern parameter.
764 public static Match Match(String input, String pattern) {
765 return Match(input, pattern, RegexOptions.None, DefaultMatchTimeout);
769 * Static version of simple Match call
773 /// Searches the input string for one or more occurrences of the text
774 /// supplied in the pattern parameter. Matching is modified with an option
778 public static Match Match(String input, String pattern, RegexOptions options) {
779 return Match(input, pattern, options, DefaultMatchTimeout);
783 #if !SILVERLIGHT || FEATURE_NETCORE
788 static Match Match(String input, String pattern, RegexOptions options, TimeSpan matchTimeout) {
789 return new Regex(pattern, options, matchTimeout, true).Match(input);
793 * Finds the first match for the regular expression starting at the beginning
794 * of the string (or at the end of the string if the regex is leftward)
798 /// Matches a regular expression with a string and returns
799 /// the precise result as a RegexMatch object.
802 public Match Match(String input) {
805 throw new ArgumentNullException("input");
807 return Match(input, UseOptionR() ? input.Length : 0);
811 * Finds the first match, starting at the specified position
814 /// Matches a regular expression with a string and returns
815 /// the precise result as a RegexMatch object.
817 public Match Match(String input, int startat) {
820 throw new ArgumentNullException("input");
822 return Run(false, -1, input, 0, input.Length, startat);
826 * Finds the first match, restricting the search to the specified interval of
832 /// regular expression with a string and returns the precise result as a
833 /// RegexMatch object.
836 public Match Match(String input, int beginning, int length) {
838 throw new ArgumentNullException("input");
840 return Run(false, -1, input, beginning, length, UseOptionR() ? beginning + length : beginning);
844 * Static version of simple Matches call
848 /// Returns all the successful matches as if Match were
849 /// called iteratively numerous times.
852 public static MatchCollection Matches(String input, String pattern) {
853 return Matches(input, pattern, RegexOptions.None, DefaultMatchTimeout);
857 * Static version of simple Matches call
861 /// Returns all the successful matches as if Match were called iteratively
865 public static MatchCollection Matches(String input, String pattern, RegexOptions options) {
866 return Matches(input, pattern, options, DefaultMatchTimeout);
869 #if !SILVERLIGHT || FEATURE_NETCORE
874 static MatchCollection Matches(String input, String pattern, RegexOptions options, TimeSpan matchTimeout) {
875 return new Regex(pattern, options, matchTimeout, true).Matches(input);
879 * Finds the first match for the regular expression starting at the beginning
880 * of the string Enumerator(or at the end of the string if the regex is leftward)
885 /// all the successful matches as if Match was called iteratively numerous
889 public MatchCollection Matches(String input) {
892 throw new ArgumentNullException("input");
894 return Matches(input, UseOptionR() ? input.Length : 0);
898 * Finds the first match, starting at the specified position
903 /// all the successful matches as if Match was called iteratively numerous
907 public MatchCollection Matches(String input, int startat) {
910 throw new ArgumentNullException("input");
912 return new MatchCollection(this, input, 0, input.Length, startat);
916 * Static version of simple Replace call
921 /// all occurrences of the pattern with the <paramref name="replacement"/> pattern, starting at
922 /// the first character in the input string.
925 public static String Replace(String input, String pattern, String replacement) {
926 return Replace(input, pattern, replacement, RegexOptions.None, DefaultMatchTimeout);
930 * Static version of simple Replace call
934 /// Replaces all occurrences of
935 /// the <paramref name="pattern "/>with the <paramref name="replacement "/>
936 /// pattern, starting at the first character in the input string.
939 public static String Replace(String input, String pattern, String replacement, RegexOptions options) {
940 return Replace(input, pattern, replacement, options, DefaultMatchTimeout);
943 #if !SILVERLIGHT || FEATURE_NETCORE
948 static String Replace(String input, String pattern, String replacement, RegexOptions options, TimeSpan matchTimeout) {
949 return new Regex(pattern, options, matchTimeout, true).Replace(input, replacement);
953 * Does the replacement
957 /// Replaces all occurrences of
958 /// the <paramref name="pattern "/> with the <paramref name="replacement"/> pattern, starting at the
959 /// first character in the input string, using the previous patten.
962 public String Replace(String input, String replacement) {
965 throw new ArgumentNullException("input");
967 return Replace(input, replacement, -1, UseOptionR() ? input.Length : 0);
971 * Does the replacement
975 /// Replaces all occurrences of the (previously defined) <paramref name="pattern "/>with the
976 /// <paramref name="replacement"/> pattern, starting at the first character in the input string.
979 public String Replace(String input, String replacement, int count) {
982 throw new ArgumentNullException("input");
984 return Replace(input, replacement, count, UseOptionR() ? input.Length : 0);
988 * Does the replacement
992 /// Replaces all occurrences of the <paramref name="pattern "/>with the recent
993 /// <paramref name="replacement"/> pattern, starting at the character position
994 /// <paramref name="startat."/>
997 public String Replace(String input, String replacement, int count, int startat) {
1000 throw new ArgumentNullException("input");
1002 if (replacement == null)
1003 throw new ArgumentNullException("replacement");
1005 // a little code to grab a cached parsed replacement object
1006 RegexReplacement repl = (RegexReplacement) replref.Get();
1008 if (repl == null || !repl.Pattern.Equals(replacement)) {
1009 repl = RegexParser.ParseReplacement(replacement, caps, capsize, capnames, this.roptions);
1010 replref.Cache(repl);
1013 return repl.Replace(this, input, count, startat);
1017 * Static version of simple Replace call
1021 /// Replaces all occurrences of the <paramref name="pattern "/>with the
1022 /// <paramref name="replacement"/> pattern
1023 /// <paramref name="."/>
1026 public static String Replace(String input, String pattern, MatchEvaluator evaluator) {
1027 return Replace(input, pattern, evaluator, RegexOptions.None, DefaultMatchTimeout);
1031 * Static version of simple Replace call
1035 /// Replaces all occurrences of the <paramref name="pattern "/>with the recent
1036 /// <paramref name="replacement"/> pattern, starting at the first character<paramref name="."/>
1039 public static String Replace(String input, String pattern, MatchEvaluator evaluator, RegexOptions options) {
1040 return Replace(input, pattern, evaluator, options, DefaultMatchTimeout);
1043 #if !SILVERLIGHT || FEATURE_NETCORE
1048 static String Replace(String input, String pattern, MatchEvaluator evaluator, RegexOptions options, TimeSpan matchTimeout) {
1049 return new Regex(pattern, options, matchTimeout, true).Replace(input, evaluator);
1053 * Does the replacement
1057 /// Replaces all occurrences of the <paramref name="pattern "/>with the recent
1058 /// <paramref name="replacement"/> pattern, starting at the first character
1059 /// position<paramref name="."/>
1062 public String Replace(String input, MatchEvaluator evaluator) {
1065 throw new ArgumentNullException("input");
1067 return Replace(input, evaluator, -1, UseOptionR() ? input.Length : 0);
1071 * Does the replacement
1075 /// Replaces all occurrences of the <paramref name="pattern "/>with the recent
1076 /// <paramref name="replacement"/> pattern, starting at the first character
1077 /// position<paramref name="."/>
1080 public String Replace(String input, MatchEvaluator evaluator, int count) {
1083 throw new ArgumentNullException("input");
1085 return Replace(input, evaluator, count, UseOptionR() ? input.Length : 0);
1089 * Does the replacement
1093 /// Replaces all occurrences of the (previouly defined) <paramref name="pattern "/>with
1094 /// the recent <paramref name="replacement"/> pattern, starting at the character
1095 /// position<paramref name=" startat."/>
1098 public String Replace(String input, MatchEvaluator evaluator, int count, int startat) {
1101 throw new ArgumentNullException("input");
1103 return RegexReplacement.Replace(evaluator, this, input, count, startat);
1107 * Static version of simple Split call
1111 /// Splits the <paramref name="input "/>string at the position defined
1112 /// by <paramref name="pattern"/>.
1115 public static String[] Split(String input, String pattern) {
1116 return Split(input, pattern, RegexOptions.None, DefaultMatchTimeout);
1120 * Static version of simple Split call
1124 /// Splits the <paramref name="input "/>string at the position defined by <paramref name="pattern"/>.
1127 public static String[] Split(String input, String pattern, RegexOptions options) {
1128 return Split(input, pattern, options, DefaultMatchTimeout);
1131 #if !SILVERLIGHT || FEATURE_NETCORE
1136 static String[] Split(String input, String pattern, RegexOptions options, TimeSpan matchTimeout) {
1137 return new Regex(pattern, options, matchTimeout, true).Split(input);
1145 /// Splits the <paramref name="input "/>string at the position defined by
1146 /// a previous <paramref name="pattern"/>
1150 public String[] Split(String input) {
1153 throw new ArgumentNullException("input");
1155 return Split(input, 0, UseOptionR() ? input.Length : 0);
1163 /// Splits the <paramref name="input "/>string at the position defined by a previous
1164 /// <paramref name="pattern"/> .
1167 public String[] Split(String input, int count) {
1170 throw new ArgumentNullException("input");
1172 return RegexReplacement.Split(this, input, count, UseOptionR() ? input.Length : 0);
1180 /// Splits the <paramref name="input "/>string at the position defined by a previous
1181 /// <paramref name="pattern"/> .
1184 public String[] Split(String input, int count, int startat) {
1186 throw new ArgumentNullException("input");
1188 return RegexReplacement.Split(this, input, count, startat);
1196 #if !DISABLE_CAS_USE
1197 [HostProtection(MayLeakOnAbort=true)]
1199 [ResourceExposure(ResourceScope.Machine)] // The AssemblyName is interesting.
1200 [ResourceConsumption(ResourceScope.Machine)]
1201 [SuppressMessage("Microsoft.Naming","CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId="assemblyname", Justification="[....]: already shipped since v1 - can't fix without causing a breaking change")]
1202 public static void CompileToAssembly(RegexCompilationInfo[] regexinfos, AssemblyName assemblyname) {
1204 CompileToAssemblyInternal(regexinfos, assemblyname, null, null);
1209 #if !DISABLE_CAS_USE
1210 [HostProtection(MayLeakOnAbort=true)]
1212 [ResourceExposure(ResourceScope.Machine)] // The AssemblyName is interesting.
1213 [ResourceConsumption(ResourceScope.Machine)]
1214 [SuppressMessage("Microsoft.Naming","CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId="assemblyname", Justification="[....]: already shipped since v1 - can't fix without causing a breaking change")]
1215 public static void CompileToAssembly(RegexCompilationInfo[] regexinfos, AssemblyName assemblyname, CustomAttributeBuilder[] attributes) {
1216 CompileToAssemblyInternal(regexinfos, assemblyname, attributes, null);
1219 #if !DISABLE_CAS_USE
1220 [HostProtection(MayLeakOnAbort=true)]
1222 [ResourceExposure(ResourceScope.Machine)]
1223 [ResourceConsumption(ResourceScope.Machine)]
1224 [SuppressMessage("Microsoft.Naming","CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId="assemblyname", Justification="[....]: already shipped since v1 - can't fix without causing a breaking change")]
1225 public static void CompileToAssembly(RegexCompilationInfo[] regexinfos, AssemblyName assemblyname, CustomAttributeBuilder[] attributes, String resourceFile) {
1226 CompileToAssemblyInternal(regexinfos, assemblyname, attributes, resourceFile);
1229 [ResourceExposure(ResourceScope.Machine)] // AssemblyName & resourceFile
1230 [ResourceConsumption(ResourceScope.Machine)]
1231 private static void CompileToAssemblyInternal (RegexCompilationInfo[] regexinfos, AssemblyName assemblyname, CustomAttributeBuilder[] attributes, String resourceFile) {
1232 if (assemblyname == null)
1233 throw new ArgumentNullException("assemblyname");
1235 if (regexinfos == null)
1236 throw new ArgumentNullException("regexinfos");
1238 RegexCompiler.CompileToAssembly(regexinfos, assemblyname, attributes, resourceFile);
1245 protected void InitializeReferences() {
1246 if (refsInitialized)
1247 throw new NotSupportedException(SR.GetString(SR.OnlyAllowedOnce));
1249 refsInitialized = true;
1250 runnerref = new ExclusiveReference();
1251 replref = new SharedReference();
1256 * Internal worker called by all the public APIs
1258 internal Match Run(bool quick, int prevlen, String input, int beginning, int length, int startat) {
1260 RegexRunner runner = null;
1262 if (startat < 0 || startat > input.Length)
1263 throw new ArgumentOutOfRangeException("start", SR.GetString(SR.BeginIndexNotNegative));
1265 if (length < 0 || length > input.Length)
1266 throw new ArgumentOutOfRangeException("length", SR.GetString(SR.LengthNotNegative));
1268 // There may be a cached runner; grab ownership of it if we can.
1270 runner = (RegexRunner)runnerref.Get();
1272 // Create a RegexRunner instance if we need to
1274 if (runner == null) {
1275 // Use the compiled RegexRunner factory if the code was compiled to MSIL
1277 if (factory != null)
1278 runner = factory.CreateInstance();
1280 runner = new RegexInterpreter(code, UseOptionInvariant() ? CultureInfo.InvariantCulture : CultureInfo.CurrentCulture);
1284 // Do the scan starting at the requested position
1285 match = runner.Scan(this, input, beginning, beginning + length, startat, prevlen, quick, internalMatchTimeout);
1287 // Release or fill the cache slot
1288 runnerref.Release(runner);
1292 if (Debug && match != null)
1299 * Find code cache based on options+pattern
1301 private static CachedCodeEntry LookupCachedAndUpdate(String key) {
1303 for (LinkedListNode<CachedCodeEntry> current = livecode.First; current != null; current = current.Next) {
1304 if (current.Value._key == key) {
1305 // If we find an entry in the cache, move it to the head at the same time.
1306 livecode.Remove(current);
1307 livecode.AddFirst(current);
1308 return current.Value;
1317 * Add current code to the cache
1319 private CachedCodeEntry CacheCode(String key) {
1320 CachedCodeEntry newcached = null;
1323 // first look for it in the cache and move it to the head
1324 for (LinkedListNode<CachedCodeEntry> current = livecode.First; current != null; current = current.Next) {
1325 if (current.Value._key == key) {
1326 livecode.Remove(current);
1327 livecode.AddFirst(current);
1328 return current.Value;
1332 // it wasn't in the cache, so we'll add a new one. Shortcut out for the case where cacheSize is zero.
1333 if (cacheSize != 0) {
1334 newcached = new CachedCodeEntry(key, capnames, capslist, code, caps, capsize, runnerref, replref);
1335 livecode.AddFirst(newcached);
1336 if (livecode.Count > cacheSize)
1337 livecode.RemoveLast();
1346 * True if the O option was set
1351 protected bool UseOptionC() {
1352 return(roptions & RegexOptions.Compiled) != 0;
1357 * True if the L option was set
1362 protected bool UseOptionR() {
1363 return(roptions & RegexOptions.RightToLeft) != 0;
1366 internal bool UseOptionInvariant() {
1367 return(roptions & RegexOptions.CultureInvariant) != 0;
1373 * True if the regex has debugging enabled
1378 internal bool Debug {
1380 return(roptions & RegexOptions.Debug) != 0;
1396 public delegate String MatchEvaluator(Match match);
1400 * Used to cache byte codes or compiled factories
1402 internal sealed class CachedCodeEntry {
1403 internal string _key;
1404 internal RegexCode _code;
1406 internal Dictionary<Int32, Int32> _caps;
1407 internal Dictionary<String, Int32> _capnames;
1409 internal Hashtable _caps;
1410 internal Hashtable _capnames;
1412 internal String[] _capslist;
1413 internal int _capsize;
1414 internal RegexRunnerFactory _factory;
1415 internal ExclusiveReference _runnerref;
1416 internal SharedReference _replref;
1419 internal CachedCodeEntry(string key, Dictionary<String, Int32> capnames, String[] capslist, RegexCode code, Dictionary<Int32, Int32> caps, int capsize, ExclusiveReference runner, SharedReference repl)
1421 internal CachedCodeEntry(string key, Hashtable capnames, String[] capslist, RegexCode code, Hashtable caps, int capsize, ExclusiveReference runner, SharedReference repl)
1426 _capnames = capnames;
1427 _capslist = capslist;
1433 _runnerref = runner;
1438 internal void AddCompiled(RegexRunnerFactory factory) {
1446 * Used to cache one exclusive runner reference
1448 internal sealed class ExclusiveReference {
1454 * Return an object and grab an exclusive lock.
1456 * If the exclusive lock can't be obtained, null is returned;
1457 * if the object can't be returned, the lock is released.
1460 internal Object Get() {
1461 // try to obtain the lock
1463 if (0 == Interlocked.Exchange(ref _locked, 1)) {
1469 // release the lock and return null if no reference
1476 // remember the reference and keep the lock
1486 * Release an object back to the cache
1488 * If the object is the one that's under lock, the lock
1491 * If there is no cached object, then the lock is obtained
1492 * and the object is placed in the cache.
1495 internal void Release(Object obj) {
1497 throw new ArgumentNullException("obj");
1499 // if this reference owns the lock, release it
1507 // if no reference owns the lock, try to cache this reference
1510 // try to obtain the lock
1512 if (0 == Interlocked.Exchange(ref _locked, 1)) {
1513 // if there's really no reference, cache this reference
1516 _ref = (RegexRunner) obj;
1528 * Used to cache a weak reference in a threadsafe way
1530 internal sealed class SharedReference {
1531 WeakReference _ref = new WeakReference(null);
1535 * Return an object from a weakref, protected by a lock.
1537 * If the exclusive lock can't be obtained, null is returned;
1539 * Note that _ref.Target is referenced only under the protection
1540 * of the lock. (Is this necessary?)
1542 internal Object Get() {
1543 if (0 == Interlocked.Exchange(ref _locked, 1)) {
1544 Object obj = _ref.Target;
1553 * Suggest an object into a weakref, protected by a lock.
1555 * Note that _ref.Target is referenced only under the protection
1556 * of the lock. (Is this necessary?)
1558 internal void Cache(Object obj) {
1559 if (0 == Interlocked.Exchange(ref _locked, 1)) {