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