8256cb7a4173c51eb316c17db828fbc612348ec8
[mono.git] / mcs / class / referencesource / System / regex / system / text / regularexpressions / Regex.cs
1 //------------------------------------------------------------------------------
2 // <copyright file="Regex.cs" company="Microsoft">
3 //     Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>                                                                
5 //------------------------------------------------------------------------------
6
7 // The Regex class represents a single compiled instance of a regular
8 // expression.
9
10 namespace System.Text.RegularExpressions {
11
12     using System;
13     using System.Threading;
14     using System.Collections;
15     using System.Reflection;
16 #if !FULL_AOT_RUNTIME
17     using System.Reflection.Emit;
18 #endif
19     using System.Globalization;
20     using System.Security.Permissions;
21     using System.Runtime.CompilerServices;
22     using System.Collections.Generic;
23     using System.Diagnostics.CodeAnalysis;
24
25 #if !SILVERLIGHT
26     using System.Runtime.Serialization;
27     using System.Runtime.Versioning;
28 #endif
29
30
31     /// <devdoc>
32     ///    <para>
33     ///       Represents an immutable, compiled regular expression. Also
34     ///       contains static methods that allow use of regular expressions without instantiating
35     ///       a Regex explicitly.
36     ///    </para>
37     /// </devdoc>
38 #if !SILVERLIGHT
39     [ Serializable() ] 
40 #endif
41     public class Regex 
42 #if !SILVERLIGHT
43     : ISerializable 
44 #endif
45     {
46
47         // Fields used by precompiled regexes
48         protected internal string pattern;
49 #if !SILVERLIGHT
50         protected internal RegexRunnerFactory factory;       // if compiled, this is the RegexRunner subclass
51 #else
52         internal RegexRunnerFactory factory;                // if compiled, this is the RegexRunner subclass
53 #endif
54
55         protected internal RegexOptions roptions;            // the top-level options from the options string
56
57
58         // *********** Match timeout fields { ***********
59
60         // We need this because time is queried using Environment.TickCount for performance reasons
61         // (Environment.TickCount returns millisecs as an int and cycles):
62         #if !SILVERLIGHT
63         [NonSerialized()]
64         #endif
65         private static readonly TimeSpan MaximumMatchTimeout = TimeSpan.FromMilliseconds(Int32.MaxValue - 1);
66
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
76         #if !FEATURE_NETCORE
77         [NonSerialized()]
78         #endif
79         public static readonly TimeSpan InfiniteMatchTimeout =
80 #if BOOTSTRAP_BASIC
81                 new TimeSpan (0, 0, 0, 0, Timeout.Infinite);    
82 #else
83                 Timeout.InfiniteTimeSpan;
84 #endif
85         #else
86         internal static readonly TimeSpan InfiniteMatchTimeout = new TimeSpan(0, 0, 0, 0, Timeout.Infinite);
87         #endif                              
88
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.
94         #if !SILVERLIGHT
95         [OptionalField(VersionAdded = 2)]        
96         protected internal
97         #else
98         internal
99         #endif
100                TimeSpan internalMatchTimeout;   // timeout for the execution of this regex
101
102
103         // During static initialisation of Regex we check 
104         private const String DefaultMatchTimeout_ConfigKeyName = "REGEX_DEFAULT_MATCH_TIMEOUT";
105
106
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()).
115         #if !SILVERLIGHT
116         [NonSerialized()]
117         #endif
118         internal static readonly TimeSpan FallbackDefaultMatchTimeout = InfiniteMatchTimeout;
119
120
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.        
125         #if !SILVERLIGHT
126         [NonSerialized()]
127         internal static readonly TimeSpan DefaultMatchTimeout = InitDefaultMatchTimeout();
128         #else
129         internal static readonly TimeSpan DefaultMatchTimeout = FallbackDefaultMatchTimeout;
130         #endif
131         
132         // *********** } match timeout fields ***********
133
134
135 #if SILVERLIGHT
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
138 #else
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;        
142 #endif
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
145
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;
150
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;
153         
154         internal const int MaxOptionShift = 10;       
155
156         protected Regex() {
157
158             // If a compiled-to-assembly RegEx was generated using an earlier version, then internalMatchTimeout will be uninitialised.
159             // Let's do it here.
160             // In distant future, when RegEx generated using pre Dev11 are not supported any more, we can remove this to aid performance:
161
162             this.internalMatchTimeout = DefaultMatchTimeout;
163         }
164
165         /*
166          * Compiles and returns a Regex object corresponding to the given pattern
167          */
168         /// <devdoc>
169         ///    <para>
170         ///       Creates and compiles a regular expression object for the specified regular
171         ///       expression.
172         ///    </para>
173         /// </devdoc>
174         public Regex(String pattern)
175             : this(pattern, RegexOptions.None, DefaultMatchTimeout, false) {
176         }
177
178         /*
179          * Returns a Regex object corresponding to the given pattern, compiled with
180          * the specified options.
181          */
182         /// <devdoc>
183         ///    <para>
184         ///       Creates and compiles a regular expression object for the
185         ///       specified regular expression
186         ///       with options that modify the pattern.
187         ///    </para>
188         /// </devdoc>
189         public Regex(String pattern, RegexOptions options)
190             : this(pattern, options, DefaultMatchTimeout, false) {
191         }
192
193         #if !SILVERLIGHT || FEATURE_NETCORE
194         public
195         #else
196         private
197         #endif
198                Regex(String pattern, RegexOptions options, TimeSpan matchTimeout)
199             : this(pattern, options, matchTimeout, false) {
200         }
201
202         private Regex(String pattern, RegexOptions options, TimeSpan matchTimeout, bool useCache) {
203             RegexTree tree;
204             CachedCodeEntry cached = null;
205             string cultureKey = null;
206
207             if (pattern == 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 | 
217 #endif
218                              RegexOptions.CultureInvariant
219 #if DBG
220                            | RegexOptions.Debug
221 #endif
222                                                )) != 0)
223                 throw new ArgumentOutOfRangeException("options");
224
225             ValidateMatchTimeout(matchTimeout);
226
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)"
231             else
232                 cultureKey = CultureInfo.CurrentCulture.ToString();
233             
234             String key = ((int) options).ToString(NumberFormatInfo.InvariantInfo) + ":" + cultureKey + ":" + pattern;
235             cached = LookupCachedAndUpdate(key);
236
237             this.pattern = pattern;
238             this.roptions = options;
239
240             this.internalMatchTimeout = matchTimeout;
241
242             if (cached == null) {
243                 // Parse the input
244                 tree = RegexParser.Parse(pattern, roptions);
245
246                 // Extract the relevant information
247                 capnames   = tree._capnames;
248                 capslist   = tree._capslist;
249                 code       = RegexWriter.Write(tree);
250                 caps       = code._caps;
251                 capsize    = code._capsize;
252
253                 InitializeReferences();
254
255                 tree = null;
256                 if (useCache)
257                     cached = CacheCode(key);
258             }
259             else {
260                 caps       = cached._caps;
261                 capnames   = cached._capnames;
262                 capslist   = cached._capslist;
263                 capsize    = cached._capsize;
264                 code       = cached._code;
265                 factory    = cached._factory;
266                 runnerref  = cached._runnerref;
267                 replref    = cached._replref;
268                 refsInitialized = true;
269             }
270
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);
275
276                 if (useCache && cached != null)
277                     cached.AddCompiled(factory);
278                 code = null;
279             }
280 #endif
281         }
282
283 #if !SILVERLIGHT
284         /* 
285          *  ISerializable constructor
286          */
287         protected Regex(SerializationInfo info, StreamingContext context)
288             : this(info.GetString("pattern"), (RegexOptions) info.GetInt32("options")) {
289
290             try {
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)
299             }
300
301         }
302
303         /* 
304          *  ISerializable method
305          */
306         /// <internalonly/>
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);
311         }
312 #endif  // !SILVERLIGHT
313
314         //* Note: "&lt;" is the XML entity for smaller ("<").
315         /// <summary>
316         /// Validates that the specified match timeout value is valid.
317         /// The valid range is <code>TimeSpan.Zero &lt; matchTimeout &lt;= Regex.MaximumMatchTimeout</code>.
318         /// </summary>
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.        
321         /// </exception>
322         #if !SILVERLIGHT
323         protected internal
324         #else
325         internal
326         #endif
327         static void ValidateMatchTimeout(TimeSpan matchTimeout) {
328
329             if (InfiniteMatchTimeout == matchTimeout)
330                 return;
331
332             // Change this to make sure timeout is not longer then Environment.Ticks cycle length:
333             if (TimeSpan.Zero < matchTimeout && matchTimeout <= MaximumMatchTimeout)
334                 return;
335
336             throw new ArgumentOutOfRangeException("matchTimeout");
337         }
338
339 #if !SILVERLIGHT
340         /// <summary>
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).
351         /// </summary>
352         /// <returns>The default RegEx matching timeout for this AppDomain</returns>        
353         private static TimeSpan InitDefaultMatchTimeout() {            
354
355             // Query AppDomain:
356             AppDomain ad = AppDomain.CurrentDomain;
357             Object defTmOut = ad.GetData(DefaultMatchTimeout_ConfigKeyName);
358
359             // If no default is specified, use fallback:
360             if (defTmOut == null)
361                 return FallbackDefaultMatchTimeout;
362
363             // If default has invalid type, throw. It will result in a TypeInitializationException:
364             if (!(defTmOut is TimeSpan)) {
365
366                 #if DBG
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);
371                 #endif
372
373                 throw new InvalidCastException(SR.GetString(SR.IllegalDefaultRegexMatchTimeoutInAppDomain, DefaultMatchTimeout_ConfigKeyName));
374             }
375
376             // Convert default value:
377             TimeSpan defaultTimeout = (TimeSpan) defTmOut;
378
379             // If default timeout is outside the valid range, throw. It will result in a TypeInitializationException:
380             try {
381                 ValidateMatchTimeout(defaultTimeout);
382
383             } catch (ArgumentOutOfRangeException) {
384
385                 #if DBG
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);
390                 #endif
391
392                 throw new ArgumentOutOfRangeException(SR.GetString(SR.IllegalDefaultRegexMatchTimeoutInAppDomain, DefaultMatchTimeout_ConfigKeyName));
393             }
394
395             // We are good:
396             return defaultTimeout;
397         }  // private static TimeSpan InitDefaultMatchTimeout
398 #endif  // !SILVERLIGHT
399
400 #if !SILVERLIGHT && !FULL_AOT_RUNTIME
401         /* 
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.
406         */
407         [
408 #if !DISABLE_CAS_USE
409             HostProtection(MayLeakOnAbort=true),
410 #endif
411             MethodImplAttribute(MethodImplOptions.NoInlining)
412         ]
413         internal RegexRunnerFactory Compile(RegexCode code, RegexOptions roptions) {
414             return RegexCompiler.Compile(code, roptions);
415         }
416 #endif  // !SILVERLIGHT
417
418         /*
419          * Escape metacharacters within the string
420          */
421         /// <devdoc>
422         ///    <para>
423         ///       Escapes 
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.)
431         ///       </para>
432         ///    </devdoc>
433         public static String Escape(String str) {
434             if (str==null)
435                 throw new ArgumentNullException("str");
436             
437             return RegexParser.Escape(str);
438         }
439
440         /*
441          * Unescape character codes within the string
442          */
443         /// <devdoc>
444         ///    <para>
445         ///       Unescapes any escaped characters in the input string.
446         ///    </para>
447         /// </devdoc>
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) {
450             if (str==null)
451                 throw new ArgumentNullException("str");
452             
453             return RegexParser.Unescape(str);
454         }
455
456         [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "Reviewed for thread-safety")]
457         public static int CacheSize {
458             get {
459                 return cacheSize;
460             }
461             set {
462                 if (value < 0)
463                     throw new ArgumentOutOfRangeException("value");
464
465                 cacheSize = value;
466                 if (livecode.Count > cacheSize) {
467                     lock (livecode) {
468                         while (livecode.Count > cacheSize)
469                             livecode.RemoveLast();
470                     }
471                 }
472             }
473         }
474         
475         /// <devdoc>
476         ///    <para>
477         ///       Returns the options passed into the constructor
478         ///    </para>
479         /// </devdoc>
480         public RegexOptions Options {
481             get { return roptions;}
482         }
483
484
485         /// <summary>
486         /// The match timeout used by this Regex instance.
487         /// </summary>
488         #if !SILVERLIGHT || FEATURE_NETCORE
489         public
490         #else
491         internal
492         #endif
493                TimeSpan MatchTimeout {
494             get { return internalMatchTimeout; }
495         }
496
497
498         /*
499          * True if the regex is leftward
500          */
501         /// <devdoc>
502         ///    <para>
503         ///       Indicates whether the regular expression matches from right to
504         ///       left.
505         ///    </para>
506         /// </devdoc>
507         public bool RightToLeft {
508             get {
509                 return UseOptionR();
510             }
511         }
512
513         /// <devdoc>
514         ///    <para>
515         ///       Returns the regular expression pattern passed into the constructor
516         ///    </para>
517         /// </devdoc>
518         public override string ToString() {
519             return pattern;
520         }
521
522         /*
523          * Returns an array of the group names that are used to capture groups
524          * in the regular expression. Only needed if the regex is not known until
525          * runtime, and one wants to extract captured groups. (Probably unusual,
526          * but supplied for completeness.)
527          */
528         /// <devdoc>
529         ///    Returns 
530         ///       the GroupNameCollection for the regular expression. This collection contains the
531         ///       set of strings used to name capturing groups in the expression. 
532         ///    </devdoc>
533         public String[] GetGroupNames() {
534             String[] result;
535
536             if (capslist == null) {
537                 int max = capsize;
538                 result = new String[max];
539
540                 for (int i = 0; i < max; i++) {
541                     result[i] = Convert.ToString(i, CultureInfo.InvariantCulture);
542                 }
543             }
544             else {
545                 result = new String[capslist.Length];
546
547                 System.Array.Copy(capslist, 0, result, 0, capslist.Length);
548             }
549
550             return result;
551         }
552
553         /*
554          * Returns an array of the group numbers that are used to capture groups
555          * in the regular expression. Only needed if the regex is not known until
556          * runtime, and one wants to extract captured groups. (Probably unusual,
557          * but supplied for completeness.)
558          */
559         /// <devdoc>
560         ///    returns 
561         ///       the integer group number corresponding to a group name. 
562         ///    </devdoc>
563         public int[] GetGroupNumbers() {
564             int[] result;
565
566             if (caps == null) {
567                 int max = capsize;
568                 result = new int[max];
569
570                 for (int i = 0; i < max; i++) {
571                     result[i] = i;
572                 }
573             }
574             else {
575                 result = new int[caps.Count];
576
577                 IDictionaryEnumerator de = caps.GetEnumerator();
578                 while (de.MoveNext()) {
579                     result[(int)de.Value] = (int)de.Key;
580                 }
581             }
582
583             return result;
584         }
585
586         /*
587          * Given a group number, maps it to a group name. Note that nubmered
588          * groups automatically get a group name that is the decimal string
589          * equivalent of its number.
590          *
591          * Returns null if the number is not a recognized group number.
592          */
593         /// <devdoc>
594         ///    <para>
595         ///       Retrieves a group name that corresponds to a group number.
596         ///    </para>
597         /// </devdoc>
598         public String GroupNameFromNumber(int i) {
599             if (capslist == null) {
600                 if (i >= 0 && i < capsize)
601                     return i.ToString(CultureInfo.InvariantCulture);
602
603                 return String.Empty;
604             }
605             else {
606                 if (caps != null) {
607 #if SILVERLIGHT
608                     if (!caps.ContainsKey(i))
609 #else
610                     Object obj = caps[i];
611                     if (obj == null)
612 #endif
613                         return String.Empty;
614
615 #if SILVERLIGHT
616                     i = caps[i];
617 #else
618                     i = (int)obj;
619 #endif
620                 }
621
622                 if (i >= 0 && i < capslist.Length)
623                     return capslist[i];
624
625                 return String.Empty;
626             }
627         }
628
629         /*
630          * Given a group name, maps it to a group number. Note that nubmered
631          * groups automatically get a group name that is the decimal string
632          * equivalent of its number.
633          *
634          * Returns -1 if the name is not a recognized group name.
635          */
636         /// <devdoc>
637         ///    <para>
638         ///       Returns a group number that corresponds to a group name.
639         ///    </para>
640         /// </devdoc>
641         public int GroupNumberFromName(String name) {
642             int result = -1;
643
644             if (name == null)
645                 throw new ArgumentNullException("name");
646
647             // look up name if we have a hashtable of names
648             if (capnames != null) {
649 #if SILVERLIGHT
650                 if (!capnames.ContainsKey(name))
651 #else
652                 Object ret = capnames[name];
653                 if (ret == null)
654 #endif
655                     return -1;
656
657 #if SILVERLIGHT
658                 return capnames[name];
659 #else
660                 return(int)ret;
661 #endif
662             }
663
664             // convert to an int if it looks like a number
665             result = 0;
666             for (int i = 0; i < name.Length; i++) {
667                 char ch = name[i];
668
669                 if (ch > '9' || ch < '0')
670                     return -1;
671
672                 result *= 10;
673                 result += (ch - '0');
674             }
675
676             // return int if it's in range
677             if (result >= 0 && result < capsize)
678                 return result;
679
680             return -1;
681         }
682
683         /*
684          * Static version of simple IsMatch call
685          */
686         ///    <devdoc>
687         ///       <para>
688         ///          Searches the input 
689         ///             string for one or more occurrences of the text supplied in the pattern
690         ///             parameter.
691         ///       </para>
692         ///    </devdoc>
693         public static bool IsMatch(String input, String pattern) {
694             return IsMatch(input, pattern, RegexOptions.None, DefaultMatchTimeout);
695         }        
696
697         /*
698          * Static version of simple IsMatch call
699          */
700         /// <devdoc>
701         ///    <para>
702         ///       Searches the input string for one or more occurrences of the text 
703         ///          supplied in the pattern parameter with matching options supplied in the options
704         ///          parameter.
705         ///       </para>
706         ///    </devdoc>
707         public static bool IsMatch(String input, String pattern, RegexOptions options) {
708             return IsMatch(input, pattern, options, DefaultMatchTimeout);
709         }
710
711         #if !SILVERLIGHT || FEATURE_NETCORE
712         public
713         #else
714         private
715         #endif
716                static bool IsMatch(String input, String pattern, RegexOptions options, TimeSpan matchTimeout) {
717             return new Regex(pattern, options, matchTimeout, true).IsMatch(input);
718         }
719
720         /*
721          * Returns true if the regex finds a match within the specified string
722          */
723         /// <devdoc>
724         ///    <para>
725         ///       Searches the input string for one or 
726         ///          more matches using the previous pattern, options, and starting
727         ///          position.
728         ///       </para>
729         ///    </devdoc>
730         public bool IsMatch(String input) {
731
732             if (input == null)
733                 throw new ArgumentNullException("input");
734
735             return IsMatch(input, UseOptionR() ? input.Length : 0);            
736         }
737
738         /*
739          * Returns true if the regex finds a match after the specified position
740          * (proceeding leftward if the regex is leftward and rightward otherwise)
741          */
742         /// <devdoc>
743         ///    <para>
744         ///       Searches the input 
745         ///          string for one or more matches using the previous pattern and options, with
746         ///          a new starting position.
747         ///    </para>
748         /// </devdoc>
749         public bool IsMatch(String input, int startat) {
750
751             if (input == null)
752                 throw new ArgumentNullException("input");
753
754             return (null == Run(true, -1, input, 0, input.Length, startat));
755         }
756
757         /*
758          * Static version of simple Match call
759          */
760         ///    <devdoc>
761         ///       <para>
762         ///          Searches the input string for one or more occurrences of the text 
763         ///             supplied in the pattern parameter.
764         ///       </para>
765         ///    </devdoc>
766         public static Match Match(String input, String pattern) {
767             return Match(input, pattern, RegexOptions.None, DefaultMatchTimeout);
768         }
769
770         /*
771          * Static version of simple Match call
772          */
773         /// <devdoc>
774         ///    <para>
775         ///       Searches the input string for one or more occurrences of the text 
776         ///          supplied in the pattern parameter. Matching is modified with an option
777         ///          string.
778         ///       </para>
779         ///    </devdoc>
780         public static Match Match(String input, String pattern, RegexOptions options) {
781             return Match(input, pattern, options, DefaultMatchTimeout);
782         }
783
784
785         #if !SILVERLIGHT || FEATURE_NETCORE
786         public
787         #else
788         private
789         #endif
790                static Match Match(String input, String pattern, RegexOptions options, TimeSpan matchTimeout) {
791             return new Regex(pattern, options, matchTimeout, true).Match(input);
792         }
793
794         /*
795          * Finds the first match for the regular expression starting at the beginning
796          * of the string (or at the end of the string if the regex is leftward)
797          */
798         /// <devdoc>
799         ///    <para>
800         ///       Matches a regular expression with a string and returns
801         ///       the precise result as a RegexMatch object.
802         ///    </para>
803         /// </devdoc>
804         public Match Match(String input) {
805
806             if (input == null)
807                 throw new ArgumentNullException("input");
808
809             return Match(input, UseOptionR() ? input.Length : 0);
810         }
811
812         /*
813          * Finds the first match, starting at the specified position
814          */
815         /// <devdoc>
816         ///    Matches a regular expression with a string and returns
817         ///    the precise result as a RegexMatch object.
818         /// </devdoc>
819         public Match Match(String input, int startat) {
820
821             if (input == null)
822                 throw new ArgumentNullException("input");
823
824             return Run(false, -1, input, 0, input.Length, startat);
825         }
826
827         /*
828          * Finds the first match, restricting the search to the specified interval of
829          * the char array.
830          */
831         /// <devdoc>
832         ///    <para>
833         ///       Matches a
834         ///       regular expression with a string and returns the precise result as a
835         ///       RegexMatch object.
836         ///    </para>
837         /// </devdoc>
838         public Match Match(String input, int beginning, int length) {
839             if (input == null)
840                 throw new ArgumentNullException("input");
841
842             return Run(false, -1, input, beginning, length, UseOptionR() ? beginning + length : beginning);
843         }
844
845         /*
846          * Static version of simple Matches call
847          */
848         ///    <devdoc>
849         ///       <para>
850         ///          Returns all the successful matches as if Match were
851         ///          called iteratively numerous times.
852         ///       </para>
853         ///    </devdoc>
854         public static MatchCollection Matches(String input, String pattern) {
855             return Matches(input, pattern, RegexOptions.None, DefaultMatchTimeout);
856         }
857
858         /*
859          * Static version of simple Matches call
860          */
861         /// <devdoc>
862         ///    <para>
863         ///       Returns all the successful matches as if Match were called iteratively
864         ///       numerous times.
865         ///    </para>
866         /// </devdoc>
867         public static MatchCollection Matches(String input, String pattern, RegexOptions options) {
868             return Matches(input, pattern, options, DefaultMatchTimeout);
869         }
870
871         #if !SILVERLIGHT || FEATURE_NETCORE
872         public
873         #else
874         private
875         #endif
876                static MatchCollection Matches(String input, String pattern, RegexOptions options, TimeSpan matchTimeout) {
877             return new Regex(pattern, options, matchTimeout, true).Matches(input);
878         }
879
880         /*
881          * Finds the first match for the regular expression starting at the beginning
882          * of the string Enumerator(or at the end of the string if the regex is leftward)
883          */
884         /// <devdoc>
885         ///    <para>
886         ///       Returns
887         ///       all the successful matches as if Match was called iteratively numerous
888         ///       times.
889         ///    </para>
890         /// </devdoc>
891         public MatchCollection Matches(String input) {
892
893             if (input == null)
894                 throw new ArgumentNullException("input");
895
896             return Matches(input, UseOptionR() ? input.Length : 0);
897         }
898
899         /*
900          * Finds the first match, starting at the specified position
901          */
902         /// <devdoc>
903         ///    <para>
904         ///       Returns
905         ///       all the successful matches as if Match was called iteratively numerous
906         ///       times.
907         ///    </para>
908         /// </devdoc>
909         public MatchCollection Matches(String input, int startat) {
910
911             if (input == null)
912                 throw new ArgumentNullException("input");
913
914             return new MatchCollection(this, input, 0, input.Length, startat);
915         }
916
917         /*
918          * Static version of simple Replace call
919          */
920         /// <devdoc>
921         ///    <para>
922         ///       Replaces 
923         ///          all occurrences of the pattern with the <paramref name="replacement"/> pattern, starting at
924         ///          the first character in the input string. 
925         ///       </para>
926         ///    </devdoc>
927         public static String Replace(String input, String pattern, String replacement) {
928             return Replace(input, pattern, replacement, RegexOptions.None, DefaultMatchTimeout);
929         }
930
931         /*
932          * Static version of simple Replace call
933          */
934         /// <devdoc>
935         ///    <para>
936         ///       Replaces all occurrences of 
937         ///          the <paramref name="pattern "/>with the <paramref name="replacement "/>
938         ///          pattern, starting at the first character in the input string. 
939         ///       </para>
940         ///    </devdoc>
941         public static String Replace(String input, String pattern, String replacement, RegexOptions options) {
942             return Replace(input, pattern, replacement, options, DefaultMatchTimeout);
943         }
944
945         #if !SILVERLIGHT || FEATURE_NETCORE
946         public
947         #else
948         private
949         #endif
950                static String Replace(String input, String pattern, String replacement, RegexOptions options, TimeSpan matchTimeout) {
951             return new Regex(pattern, options, matchTimeout, true).Replace(input, replacement);
952         }
953
954         /*
955          * Does the replacement
956          */
957         /// <devdoc>
958         ///    <para>
959         ///       Replaces all occurrences of 
960         ///          the <paramref name="pattern "/> with the <paramref name="replacement"/> pattern, starting at the
961         ///          first character in the input string, using the previous patten. 
962         ///       </para>
963         ///    </devdoc>
964         public String Replace(String input, String replacement) {
965
966             if (input == null)
967                 throw new ArgumentNullException("input");
968
969             return Replace(input, replacement, -1, UseOptionR() ? input.Length : 0);
970         }
971
972         /*
973          * Does the replacement
974          */
975         /// <devdoc>
976         ///    <para>
977         ///    Replaces all occurrences of the (previously defined) <paramref name="pattern "/>with the 
978         ///    <paramref name="replacement"/> pattern, starting at the first character in the input string. 
979         /// </para>
980         /// </devdoc>
981         public String Replace(String input, String replacement, int count) {
982
983             if (input == null)
984                 throw new ArgumentNullException("input");
985
986             return Replace(input, replacement, count, UseOptionR() ? input.Length : 0);
987         }
988
989         /*
990          * Does the replacement
991          */
992         /// <devdoc>
993         ///    <para>
994         ///    Replaces all occurrences of the <paramref name="pattern "/>with the recent 
995         ///    <paramref name="replacement"/> pattern, starting at the character position 
996         ///    <paramref name="startat."/>
997         /// </para>
998         /// </devdoc>
999         public String Replace(String input, String replacement, int count, int startat) {
1000
1001             if (input == null)
1002                 throw new ArgumentNullException("input");
1003
1004             if (replacement == null)
1005                 throw new ArgumentNullException("replacement");
1006
1007             // a little code to grab a cached parsed replacement object
1008             RegexReplacement repl = (RegexReplacement) replref.Get();
1009
1010             if (repl == null || !repl.Pattern.Equals(replacement)) {
1011                 repl = RegexParser.ParseReplacement(replacement, caps, capsize, capnames, this.roptions);
1012                 replref.Cache(repl);
1013             }
1014
1015             return repl.Replace(this, input, count, startat);
1016         }
1017
1018         /*
1019          * Static version of simple Replace call
1020          */
1021         /// <devdoc>
1022         ///    <para>
1023         ///    Replaces all occurrences of the <paramref name="pattern "/>with the 
1024         ///    <paramref name="replacement"/> pattern 
1025         ///    <paramref name="."/>
1026         /// </para>
1027         /// </devdoc>
1028         public static String Replace(String input, String pattern, MatchEvaluator evaluator) {
1029             return Replace(input, pattern, evaluator, RegexOptions.None, DefaultMatchTimeout);
1030         }
1031
1032         /*
1033          * Static version of simple Replace call
1034          */
1035         /// <devdoc>
1036         ///    <para>
1037         ///    Replaces all occurrences of the <paramref name="pattern "/>with the recent 
1038         ///    <paramref name="replacement"/> pattern, starting at the first character<paramref name="."/>
1039         /// </para>
1040         /// </devdoc>
1041         public static String Replace(String input, String pattern, MatchEvaluator evaluator, RegexOptions options) {
1042             return Replace(input, pattern, evaluator, options, DefaultMatchTimeout);
1043         }
1044
1045         #if !SILVERLIGHT || FEATURE_NETCORE
1046         public
1047         #else
1048         private
1049         #endif
1050                static String Replace(String input, String pattern, MatchEvaluator evaluator, RegexOptions options, TimeSpan matchTimeout) {
1051             return new Regex(pattern, options, matchTimeout, true).Replace(input, evaluator);
1052         }
1053
1054         /*
1055          * Does the replacement
1056          */
1057         /// <devdoc>
1058         ///    <para>
1059         ///    Replaces all occurrences of the <paramref name="pattern "/>with the recent 
1060         ///    <paramref name="replacement"/> pattern, starting at the first character 
1061         ///    position<paramref name="."/>
1062         /// </para>
1063         /// </devdoc>
1064         public String Replace(String input, MatchEvaluator evaluator) {
1065
1066             if (input == null)
1067                 throw new ArgumentNullException("input");
1068
1069             return Replace(input, evaluator, -1, UseOptionR() ? input.Length : 0);
1070         }
1071
1072         /*
1073          * Does the replacement
1074          */
1075         /// <devdoc>
1076         ///    <para>
1077         ///    Replaces all occurrences of the <paramref name="pattern "/>with the recent 
1078         ///    <paramref name="replacement"/> pattern, starting at the first character 
1079         ///    position<paramref name="."/>
1080         /// </para>
1081         /// </devdoc>
1082         public String Replace(String input, MatchEvaluator evaluator, int count) {
1083
1084             if (input == null)
1085                 throw new ArgumentNullException("input");
1086
1087             return Replace(input, evaluator, count, UseOptionR() ? input.Length : 0);
1088         }
1089
1090         /*
1091          * Does the replacement
1092          */
1093         /// <devdoc>
1094         ///    <para>
1095         ///    Replaces all occurrences of the (previouly defined) <paramref name="pattern "/>with 
1096         ///       the recent <paramref name="replacement"/> pattern, starting at the character
1097         ///    position<paramref name=" startat."/> 
1098         /// </para>
1099         /// </devdoc>
1100         public String Replace(String input, MatchEvaluator evaluator, int count, int startat) {
1101
1102             if (input == null)
1103                 throw new ArgumentNullException("input");
1104
1105             return RegexReplacement.Replace(evaluator, this, input, count, startat);
1106         }
1107
1108         /*
1109          * Static version of simple Split call
1110          */
1111         ///    <devdoc>
1112         ///       <para>
1113         ///          Splits the <paramref name="input "/>string at the position defined
1114         ///          by <paramref name="pattern"/>.
1115         ///       </para>
1116         ///    </devdoc>
1117         public static String[] Split(String input, String pattern) {
1118             return Split(input, pattern, RegexOptions.None, DefaultMatchTimeout);
1119         }
1120
1121         /*
1122          * Static version of simple Split call
1123          */
1124         /// <devdoc>
1125         ///    <para>
1126         ///       Splits the <paramref name="input "/>string at the position defined by <paramref name="pattern"/>.
1127         ///    </para>
1128         /// </devdoc>
1129         public static String[] Split(String input, String pattern, RegexOptions options) {
1130             return Split(input, pattern, options, DefaultMatchTimeout);
1131         }
1132
1133         #if !SILVERLIGHT || FEATURE_NETCORE
1134         public
1135         #else
1136         private
1137         #endif
1138                static String[] Split(String input, String pattern, RegexOptions options, TimeSpan matchTimeout) {
1139             return new Regex(pattern, options, matchTimeout, true).Split(input);
1140         }
1141
1142         /*
1143          * Does a split
1144          */
1145         /// <devdoc>
1146         ///    <para>
1147         ///       Splits the <paramref name="input "/>string at the position defined by
1148         ///       a previous <paramref name="pattern"/>
1149         ///       .
1150         ///    </para>
1151         /// </devdoc>
1152         public String[] Split(String input) {
1153
1154             if (input == null)
1155                 throw new ArgumentNullException("input");
1156
1157             return Split(input, 0, UseOptionR() ? input.Length : 0);
1158         }
1159
1160         /*
1161          * Does a split
1162          */
1163         /// <devdoc>
1164         ///    <para>
1165         ///       Splits the <paramref name="input "/>string at the position defined by a previous
1166         ///    <paramref name="pattern"/> . 
1167         ///    </para>
1168         /// </devdoc>
1169         public String[] Split(String input, int count) {
1170
1171             if (input == null)
1172                 throw new ArgumentNullException("input");
1173
1174             return RegexReplacement.Split(this, input, count, UseOptionR() ? input.Length : 0);
1175         }
1176
1177         /*
1178          * Does a split
1179          */
1180         /// <devdoc>
1181         ///    <para>
1182         ///       Splits the <paramref name="input "/>string at the position defined by a previous
1183         ///    <paramref name="pattern"/> . 
1184         ///    </para>
1185         /// </devdoc>
1186         public String[] Split(String input, int count, int startat) {
1187             if (input==null)
1188                 throw new ArgumentNullException("input");
1189
1190             return RegexReplacement.Split(this, input, count, startat);
1191         }
1192
1193
1194         
1195 #if !(SILVERLIGHT || FULL_AOT_RUNTIME)
1196         /// <devdoc>
1197         /// </devdoc>
1198 #if !DISABLE_CAS_USE
1199         [HostProtection(MayLeakOnAbort=true)]
1200 #endif
1201         [ResourceExposure(ResourceScope.Machine)] // The AssemblyName is interesting.
1202         [ResourceConsumption(ResourceScope.Machine)]
1203         [SuppressMessage("Microsoft.Naming","CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId="assemblyname", Justification="[....]: already shipped since v1 - can't fix without causing a breaking change")]
1204         public static void CompileToAssembly(RegexCompilationInfo[] regexinfos, AssemblyName assemblyname) {
1205         
1206             CompileToAssemblyInternal(regexinfos, assemblyname, null, null);
1207         }
1208
1209         /// <devdoc>
1210         /// </devdoc>
1211 #if !DISABLE_CAS_USE
1212         [HostProtection(MayLeakOnAbort=true)]
1213 #endif
1214         [ResourceExposure(ResourceScope.Machine)] // The AssemblyName is interesting.
1215         [ResourceConsumption(ResourceScope.Machine)]
1216         [SuppressMessage("Microsoft.Naming","CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId="assemblyname", Justification="[....]: already shipped since v1 - can't fix without causing a breaking change")]
1217         public static void CompileToAssembly(RegexCompilationInfo[] regexinfos, AssemblyName assemblyname, CustomAttributeBuilder[] attributes) {
1218             CompileToAssemblyInternal(regexinfos, assemblyname, attributes, null);
1219         }
1220
1221 #if !DISABLE_CAS_USE
1222         [HostProtection(MayLeakOnAbort=true)]
1223 #endif
1224         [ResourceExposure(ResourceScope.Machine)]
1225         [ResourceConsumption(ResourceScope.Machine)]
1226         [SuppressMessage("Microsoft.Naming","CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId="assemblyname", Justification="[....]: already shipped since v1 - can't fix without causing a breaking change")]
1227         public static void CompileToAssembly(RegexCompilationInfo[] regexinfos, AssemblyName assemblyname, CustomAttributeBuilder[] attributes, String resourceFile) {
1228             CompileToAssemblyInternal(regexinfos, assemblyname, attributes, resourceFile);
1229         }
1230
1231         [ResourceExposure(ResourceScope.Machine)]  // AssemblyName & resourceFile
1232         [ResourceConsumption(ResourceScope.Machine)]
1233         private static void CompileToAssemblyInternal (RegexCompilationInfo[] regexinfos, AssemblyName assemblyname, CustomAttributeBuilder[] attributes, String resourceFile) {
1234             if (assemblyname == null)
1235                 throw new ArgumentNullException("assemblyname");
1236
1237             if (regexinfos == null)
1238                 throw new ArgumentNullException("regexinfos");
1239         
1240             RegexCompiler.CompileToAssembly(regexinfos, assemblyname, attributes, resourceFile);
1241         }
1242         
1243 #endif
1244
1245         /// <devdoc>
1246         /// </devdoc>
1247         protected void InitializeReferences() {
1248             if (refsInitialized)
1249                 throw new NotSupportedException(SR.GetString(SR.OnlyAllowedOnce));
1250             
1251             refsInitialized = true;
1252             runnerref  = new ExclusiveReference();
1253             replref    = new SharedReference();
1254         }
1255
1256         
1257         /*
1258          * Internal worker called by all the public APIs
1259          */
1260         internal Match Run(bool quick, int prevlen, String input, int beginning, int length, int startat) {
1261             Match match;
1262             RegexRunner runner = null;
1263
1264             if (startat < 0 || startat > input.Length)
1265                 throw new ArgumentOutOfRangeException("start", SR.GetString(SR.BeginIndexNotNegative));
1266
1267             if (length < 0 || length > input.Length)
1268                 throw new ArgumentOutOfRangeException("length", SR.GetString(SR.LengthNotNegative));
1269
1270             // There may be a cached runner; grab ownership of it if we can.
1271
1272             runner = (RegexRunner)runnerref.Get();
1273
1274             // Create a RegexRunner instance if we need to
1275
1276             if (runner == null) {
1277                 // Use the compiled RegexRunner factory if the code was compiled to MSIL
1278
1279                 if (factory != null)
1280                     runner = factory.CreateInstance();
1281                 else
1282                     runner = new RegexInterpreter(code, UseOptionInvariant() ? CultureInfo.InvariantCulture : CultureInfo.CurrentCulture);
1283             }
1284
1285             try {
1286                 // Do the scan starting at the requested position            
1287                 match = runner.Scan(this, input, beginning, beginning + length, startat, prevlen, quick, internalMatchTimeout);
1288             } finally {
1289                 // Release or fill the cache slot
1290                 runnerref.Release(runner);
1291             }
1292
1293 #if DBG
1294             if (Debug && match != null)
1295                 match.Dump();
1296 #endif
1297             return match;
1298         }
1299
1300         /*
1301          * Find code cache based on options+pattern
1302          */
1303         private static CachedCodeEntry LookupCachedAndUpdate(String key) {
1304             lock (livecode) {
1305                 for (LinkedListNode<CachedCodeEntry> current = livecode.First; current != null; current = current.Next) {
1306                     if (current.Value._key == key) {
1307                         // If we find an entry in the cache, move it to the head at the same time. 
1308                         livecode.Remove(current);
1309                         livecode.AddFirst(current);
1310                         return current.Value;
1311                     }
1312                 }
1313             }
1314
1315             return null;
1316         }
1317
1318         /*
1319          * Add current code to the cache
1320          */
1321         private CachedCodeEntry CacheCode(String key) {
1322             CachedCodeEntry newcached = null;
1323
1324             lock (livecode) {
1325                 // first look for it in the cache and move it to the head
1326                 for (LinkedListNode<CachedCodeEntry> current = livecode.First; current != null; current = current.Next) {
1327                     if (current.Value._key == key) {
1328                         livecode.Remove(current);
1329                         livecode.AddFirst(current);
1330                         return current.Value;
1331                     }
1332                 }
1333
1334                 // it wasn't in the cache, so we'll add a new one.  Shortcut out for the case where cacheSize is zero.
1335                 if (cacheSize != 0) {
1336                     newcached = new CachedCodeEntry(key, capnames, capslist, code, caps, capsize, runnerref, replref);
1337                     livecode.AddFirst(newcached);
1338                     if (livecode.Count > cacheSize)
1339                         livecode.RemoveLast();
1340                 }
1341             }
1342
1343             return newcached;
1344         }
1345
1346 #if !(SILVERLIGHT||FULL_AOT_RUNTIME)
1347         /*
1348          * True if the O option was set
1349          */
1350         /// <internalonly/>
1351         /// <devdoc>
1352         /// </devdoc>
1353         protected bool UseOptionC() {
1354                 /* Mono: Set to false until we investigate  https://bugzilla.xamarin.com/show_bug.cgi?id=25671 */
1355             return false;
1356             return(roptions & RegexOptions.Compiled) != 0;
1357         }
1358 #endif
1359
1360         /*
1361          * True if the L option was set
1362          */
1363         /// <internalonly/>
1364         /// <devdoc>
1365         /// </devdoc>
1366         protected bool UseOptionR() {
1367             return(roptions & RegexOptions.RightToLeft) != 0;
1368         }
1369
1370         internal bool UseOptionInvariant() {
1371             return(roptions & RegexOptions.CultureInvariant) != 0;
1372         }
1373             
1374
1375 #if DBG
1376         /*
1377          * True if the regex has debugging enabled
1378          */
1379         /// <internalonly/>
1380         /// <devdoc>
1381         /// </devdoc>
1382         internal bool Debug {
1383             get {
1384                 return(roptions & RegexOptions.Debug) != 0;
1385             }
1386         }
1387
1388 #endif
1389     }
1390
1391
1392     /*
1393      * Callback class
1394      */
1395     /// <devdoc>
1396     /// </devdoc>
1397 #if !SILVERLIGHT
1398     [ Serializable() ] 
1399 #endif
1400     public delegate String MatchEvaluator(Match match);
1401
1402
1403     /*
1404      * Used to cache byte codes or compiled factories
1405      */
1406     internal sealed class CachedCodeEntry {
1407         internal string _key;
1408         internal RegexCode _code;
1409 #if SILVERLIGHT
1410         internal Dictionary<Int32, Int32> _caps;
1411         internal Dictionary<String, Int32> _capnames;
1412 #else
1413         internal Hashtable _caps;
1414         internal Hashtable _capnames;
1415 #endif
1416         internal String[]  _capslist;
1417         internal int       _capsize;
1418         internal RegexRunnerFactory _factory;
1419         internal ExclusiveReference _runnerref;
1420         internal SharedReference _replref;
1421
1422 #if SILVERLIGHT
1423         internal CachedCodeEntry(string key, Dictionary<String, Int32> capnames, String[] capslist, RegexCode code, Dictionary<Int32, Int32> caps, int capsize, ExclusiveReference runner, SharedReference repl)
1424 #else
1425         internal CachedCodeEntry(string key, Hashtable capnames, String[] capslist, RegexCode code, Hashtable caps, int capsize, ExclusiveReference runner, SharedReference repl)
1426 #endif
1427         {
1428
1429             _key        = key;
1430             _capnames   = capnames;
1431             _capslist   = capslist;
1432
1433             _code       = code;
1434             _caps       = caps;
1435             _capsize    = capsize;
1436
1437             _runnerref     = runner;
1438             _replref       = repl;
1439         }
1440
1441 #if !SILVERLIGHT
1442         internal void AddCompiled(RegexRunnerFactory factory) {
1443             _factory = factory;
1444             _code = null;
1445         }
1446 #endif
1447     }
1448
1449     /*
1450      * Used to cache one exclusive runner reference
1451      */
1452     internal sealed class ExclusiveReference {
1453         RegexRunner _ref;
1454         Object _obj;
1455         int _locked;
1456
1457         /*
1458          * Return an object and grab an exclusive lock.
1459          *
1460          * If the exclusive lock can't be obtained, null is returned;
1461          * if the object can't be returned, the lock is released.
1462          *
1463          */
1464         internal Object Get() {
1465             // try to obtain the lock
1466
1467             if (0 == Interlocked.Exchange(ref _locked, 1)) {
1468                 // grab reference
1469
1470                    
1471                 Object obj = _ref;
1472
1473                 // release the lock and return null if no reference
1474
1475                 if (obj == null) {
1476                     _locked = 0;
1477                     return null;
1478                 }
1479
1480                 // remember the reference and keep the lock
1481
1482                 _obj = obj;
1483                 return obj;
1484             }
1485
1486             return null;
1487         }
1488
1489         /*
1490          * Release an object back to the cache
1491          *
1492          * If the object is the one that's under lock, the lock
1493          * is released.
1494          *
1495          * If there is no cached object, then the lock is obtained
1496          * and the object is placed in the cache.
1497          *
1498          */
1499         internal void Release(Object obj) {
1500             if (obj == null)
1501                 throw new ArgumentNullException("obj");
1502
1503             // if this reference owns the lock, release it
1504
1505             if (_obj == obj) {
1506                 _obj = null;
1507                 _locked = 0;
1508                 return;
1509             }
1510
1511             // if no reference owns the lock, try to cache this reference
1512
1513             if (_obj == null) {
1514                 // try to obtain the lock
1515
1516                 if (0 == Interlocked.Exchange(ref _locked, 1)) {
1517                     // if there's really no reference, cache this reference
1518
1519                     if (_ref == null)
1520                         _ref = (RegexRunner) obj;
1521
1522                     // release the lock
1523
1524                     _locked = 0;
1525                     return;
1526                 }
1527             }
1528         }
1529     }
1530
1531     /*
1532      * Used to cache a weak reference in a threadsafe way
1533      */
1534     internal sealed class SharedReference {
1535         WeakReference _ref = new WeakReference(null);
1536         int _locked;
1537
1538         /*
1539          * Return an object from a weakref, protected by a lock.
1540          *
1541          * If the exclusive lock can't be obtained, null is returned;
1542          *
1543          * Note that _ref.Target is referenced only under the protection
1544          * of the lock. (Is this necessary?)
1545          */
1546         internal  Object Get() {
1547             if (0 == Interlocked.Exchange(ref _locked, 1)) {
1548                 Object obj = _ref.Target;
1549                 _locked = 0;
1550                 return obj;
1551             }
1552
1553             return null;
1554         }
1555
1556         /*
1557          * Suggest an object into a weakref, protected by a lock.
1558          *
1559          * Note that _ref.Target is referenced only under the protection
1560          * of the lock. (Is this necessary?)
1561          */
1562         internal void Cache(Object obj) {
1563             if (0 == Interlocked.Exchange(ref _locked, 1)) {
1564                 _ref.Target = obj;
1565                 _locked = 0;
1566             }
1567         }
1568     }
1569
1570 }