Merge pull request #4169 from evincarofautumn/fix-xmm-scanning-mac-x86
[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 MONO_FEATURE_CAS
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         [CLSCompliant (false)]
476         protected IDictionary Caps
477         {
478             get
479             {
480                 var dict = new Dictionary<int, int>();
481
482                 foreach (int key in caps.Keys)
483                 {
484                     dict.Add (key, (int)caps[key]);
485                 }
486
487                 return dict;
488             }
489             set
490             {
491                 if (value == null)
492                     throw new ArgumentNullException("value");
493
494  
495                 caps = new Hashtable (value.Count);
496                 foreach (DictionaryEntry entry in value)
497                 {
498                     caps[(int)entry.Key] = (int)entry.Value;
499                 }
500             }
501         }
502
503         [CLSCompliant (false)]
504         protected IDictionary CapNames
505         {
506             get
507             {
508                 var dict = new Dictionary<string, int>();
509
510                 foreach (string key in capnames.Keys)
511                 {
512                     dict.Add (key, (int)capnames[key]);
513                 }
514
515                 return dict;
516             }
517             set
518             {
519                 if (value == null)
520                     throw new ArgumentNullException("value");
521
522                 capnames = new Hashtable (value.Count);
523                 foreach (DictionaryEntry entry in value)
524                 {
525                     capnames[(string)entry.Key] = (int)entry.Value;
526                 }
527             }
528         }
529
530         /// <devdoc>
531         ///    <para>
532         ///       Returns the options passed into the constructor
533         ///    </para>
534         /// </devdoc>
535         public RegexOptions Options {
536             get { return roptions;}
537         }
538
539
540         /// <summary>
541         /// The match timeout used by this Regex instance.
542         /// </summary>
543         #if !SILVERLIGHT || FEATURE_NETCORE
544         public
545         #else
546         internal
547         #endif
548                TimeSpan MatchTimeout {
549             get { return internalMatchTimeout; }
550         }
551
552
553         /*
554          * True if the regex is leftward
555          */
556         /// <devdoc>
557         ///    <para>
558         ///       Indicates whether the regular expression matches from right to
559         ///       left.
560         ///    </para>
561         /// </devdoc>
562         public bool RightToLeft {
563             get {
564                 return UseOptionR();
565             }
566         }
567
568         /// <devdoc>
569         ///    <para>
570         ///       Returns the regular expression pattern passed into the constructor
571         ///    </para>
572         /// </devdoc>
573         public override string ToString() {
574             return pattern;
575         }
576
577         /*
578          * Returns an array of the group names that are used to capture groups
579          * in the regular expression. Only needed if the regex is not known until
580          * runtime, and one wants to extract captured groups. (Probably unusual,
581          * but supplied for completeness.)
582          */
583         /// <devdoc>
584         ///    Returns 
585         ///       the GroupNameCollection for the regular expression. This collection contains the
586         ///       set of strings used to name capturing groups in the expression. 
587         ///    </devdoc>
588         public String[] GetGroupNames() {
589             String[] result;
590
591             if (capslist == null) {
592                 int max = capsize;
593                 result = new String[max];
594
595                 for (int i = 0; i < max; i++) {
596                     result[i] = Convert.ToString(i, CultureInfo.InvariantCulture);
597                 }
598             }
599             else {
600                 result = new String[capslist.Length];
601
602                 System.Array.Copy(capslist, 0, result, 0, capslist.Length);
603             }
604
605             return result;
606         }
607
608         /*
609          * Returns an array of the group numbers that are used to capture groups
610          * in the regular expression. Only needed if the regex is not known until
611          * runtime, and one wants to extract captured groups. (Probably unusual,
612          * but supplied for completeness.)
613          */
614         /// <devdoc>
615         ///    returns 
616         ///       the integer group number corresponding to a group name. 
617         ///    </devdoc>
618         public int[] GetGroupNumbers() {
619             int[] result;
620
621             if (caps == null) {
622                 int max = capsize;
623                 result = new int[max];
624
625                 for (int i = 0; i < max; i++) {
626                     result[i] = i;
627                 }
628             }
629             else {
630                 result = new int[caps.Count];
631
632                 IDictionaryEnumerator de = caps.GetEnumerator();
633                 while (de.MoveNext()) {
634                     result[(int)de.Value] = (int)de.Key;
635                 }
636             }
637
638             return result;
639         }
640
641         /*
642          * Given a group number, maps it to a group name. Note that nubmered
643          * groups automatically get a group name that is the decimal string
644          * equivalent of its number.
645          *
646          * Returns null if the number is not a recognized group number.
647          */
648         /// <devdoc>
649         ///    <para>
650         ///       Retrieves a group name that corresponds to a group number.
651         ///    </para>
652         /// </devdoc>
653         public String GroupNameFromNumber(int i) {
654             if (capslist == null) {
655                 if (i >= 0 && i < capsize)
656                     return i.ToString(CultureInfo.InvariantCulture);
657
658                 return String.Empty;
659             }
660             else {
661                 if (caps != null) {
662 #if SILVERLIGHT
663                     if (!caps.ContainsKey(i))
664 #else
665                     Object obj = caps[i];
666                     if (obj == null)
667 #endif
668                         return String.Empty;
669
670 #if SILVERLIGHT
671                     i = caps[i];
672 #else
673                     i = (int)obj;
674 #endif
675                 }
676
677                 if (i >= 0 && i < capslist.Length)
678                     return capslist[i];
679
680                 return String.Empty;
681             }
682         }
683
684         /*
685          * Given a group name, maps it to a group number. Note that nubmered
686          * groups automatically get a group name that is the decimal string
687          * equivalent of its number.
688          *
689          * Returns -1 if the name is not a recognized group name.
690          */
691         /// <devdoc>
692         ///    <para>
693         ///       Returns a group number that corresponds to a group name.
694         ///    </para>
695         /// </devdoc>
696         public int GroupNumberFromName(String name) {
697             int result = -1;
698
699             if (name == null)
700                 throw new ArgumentNullException("name");
701
702             // look up name if we have a hashtable of names
703             if (capnames != null) {
704 #if SILVERLIGHT
705                 if (!capnames.ContainsKey(name))
706 #else
707                 Object ret = capnames[name];
708                 if (ret == null)
709 #endif
710                     return -1;
711
712 #if SILVERLIGHT
713                 return capnames[name];
714 #else
715                 return(int)ret;
716 #endif
717             }
718
719             // convert to an int if it looks like a number
720             result = 0;
721             for (int i = 0; i < name.Length; i++) {
722                 char ch = name[i];
723
724                 if (ch > '9' || ch < '0')
725                     return -1;
726
727                 result *= 10;
728                 result += (ch - '0');
729             }
730
731             // return int if it's in range
732             if (result >= 0 && result < capsize)
733                 return result;
734
735             return -1;
736         }
737
738         /*
739          * Static version of simple IsMatch call
740          */
741         ///    <devdoc>
742         ///       <para>
743         ///          Searches the input 
744         ///             string for one or more occurrences of the text supplied in the pattern
745         ///             parameter.
746         ///       </para>
747         ///    </devdoc>
748         public static bool IsMatch(String input, String pattern) {
749             return IsMatch(input, pattern, RegexOptions.None, DefaultMatchTimeout);
750         }        
751
752         /*
753          * Static version of simple IsMatch call
754          */
755         /// <devdoc>
756         ///    <para>
757         ///       Searches the input string for one or more occurrences of the text 
758         ///          supplied in the pattern parameter with matching options supplied in the options
759         ///          parameter.
760         ///       </para>
761         ///    </devdoc>
762         public static bool IsMatch(String input, String pattern, RegexOptions options) {
763             return IsMatch(input, pattern, options, DefaultMatchTimeout);
764         }
765
766         #if !SILVERLIGHT || FEATURE_NETCORE
767         public
768         #else
769         private
770         #endif
771                static bool IsMatch(String input, String pattern, RegexOptions options, TimeSpan matchTimeout) {
772             return new Regex(pattern, options, matchTimeout, true).IsMatch(input);
773         }
774
775         /*
776          * Returns true if the regex finds a match within the specified string
777          */
778         /// <devdoc>
779         ///    <para>
780         ///       Searches the input string for one or 
781         ///          more matches using the previous pattern, options, and starting
782         ///          position.
783         ///       </para>
784         ///    </devdoc>
785         public bool IsMatch(String input) {
786
787             if (input == null)
788                 throw new ArgumentNullException("input");
789
790             return IsMatch(input, UseOptionR() ? input.Length : 0);            
791         }
792
793         /*
794          * Returns true if the regex finds a match after the specified position
795          * (proceeding leftward if the regex is leftward and rightward otherwise)
796          */
797         /// <devdoc>
798         ///    <para>
799         ///       Searches the input 
800         ///          string for one or more matches using the previous pattern and options, with
801         ///          a new starting position.
802         ///    </para>
803         /// </devdoc>
804         public bool IsMatch(String input, int startat) {
805
806             if (input == null)
807                 throw new ArgumentNullException("input");
808
809             return (null == Run(true, -1, input, 0, input.Length, startat));
810         }
811
812         /*
813          * Static version of simple Match call
814          */
815         ///    <devdoc>
816         ///       <para>
817         ///          Searches the input string for one or more occurrences of the text 
818         ///             supplied in the pattern parameter.
819         ///       </para>
820         ///    </devdoc>
821         public static Match Match(String input, String pattern) {
822             return Match(input, pattern, RegexOptions.None, DefaultMatchTimeout);
823         }
824
825         /*
826          * Static version of simple Match call
827          */
828         /// <devdoc>
829         ///    <para>
830         ///       Searches the input string for one or more occurrences of the text 
831         ///          supplied in the pattern parameter. Matching is modified with an option
832         ///          string.
833         ///       </para>
834         ///    </devdoc>
835         public static Match Match(String input, String pattern, RegexOptions options) {
836             return Match(input, pattern, options, DefaultMatchTimeout);
837         }
838
839
840         #if !SILVERLIGHT || FEATURE_NETCORE
841         public
842         #else
843         private
844         #endif
845                static Match Match(String input, String pattern, RegexOptions options, TimeSpan matchTimeout) {
846             return new Regex(pattern, options, matchTimeout, true).Match(input);
847         }
848
849         /*
850          * Finds the first match for the regular expression starting at the beginning
851          * of the string (or at the end of the string if the regex is leftward)
852          */
853         /// <devdoc>
854         ///    <para>
855         ///       Matches a regular expression with a string and returns
856         ///       the precise result as a RegexMatch object.
857         ///    </para>
858         /// </devdoc>
859         public Match Match(String input) {
860
861             if (input == null)
862                 throw new ArgumentNullException("input");
863
864             return Match(input, UseOptionR() ? input.Length : 0);
865         }
866
867         /*
868          * Finds the first match, starting at the specified position
869          */
870         /// <devdoc>
871         ///    Matches a regular expression with a string and returns
872         ///    the precise result as a RegexMatch object.
873         /// </devdoc>
874         public Match Match(String input, int startat) {
875
876             if (input == null)
877                 throw new ArgumentNullException("input");
878
879             return Run(false, -1, input, 0, input.Length, startat);
880         }
881
882         /*
883          * Finds the first match, restricting the search to the specified interval of
884          * the char array.
885          */
886         /// <devdoc>
887         ///    <para>
888         ///       Matches a
889         ///       regular expression with a string and returns the precise result as a
890         ///       RegexMatch object.
891         ///    </para>
892         /// </devdoc>
893         public Match Match(String input, int beginning, int length) {
894             if (input == null)
895                 throw new ArgumentNullException("input");
896
897             return Run(false, -1, input, beginning, length, UseOptionR() ? beginning + length : beginning);
898         }
899
900         /*
901          * Static version of simple Matches call
902          */
903         ///    <devdoc>
904         ///       <para>
905         ///          Returns all the successful matches as if Match were
906         ///          called iteratively numerous times.
907         ///       </para>
908         ///    </devdoc>
909         public static MatchCollection Matches(String input, String pattern) {
910             return Matches(input, pattern, RegexOptions.None, DefaultMatchTimeout);
911         }
912
913         /*
914          * Static version of simple Matches call
915          */
916         /// <devdoc>
917         ///    <para>
918         ///       Returns all the successful matches as if Match were called iteratively
919         ///       numerous times.
920         ///    </para>
921         /// </devdoc>
922         public static MatchCollection Matches(String input, String pattern, RegexOptions options) {
923             return Matches(input, pattern, options, DefaultMatchTimeout);
924         }
925
926         #if !SILVERLIGHT || FEATURE_NETCORE
927         public
928         #else
929         private
930         #endif
931                static MatchCollection Matches(String input, String pattern, RegexOptions options, TimeSpan matchTimeout) {
932             return new Regex(pattern, options, matchTimeout, true).Matches(input);
933         }
934
935         /*
936          * Finds the first match for the regular expression starting at the beginning
937          * of the string Enumerator(or at the end of the string if the regex is leftward)
938          */
939         /// <devdoc>
940         ///    <para>
941         ///       Returns
942         ///       all the successful matches as if Match was called iteratively numerous
943         ///       times.
944         ///    </para>
945         /// </devdoc>
946         public MatchCollection Matches(String input) {
947
948             if (input == null)
949                 throw new ArgumentNullException("input");
950
951             return Matches(input, UseOptionR() ? input.Length : 0);
952         }
953
954         /*
955          * Finds the first match, starting at the specified position
956          */
957         /// <devdoc>
958         ///    <para>
959         ///       Returns
960         ///       all the successful matches as if Match was called iteratively numerous
961         ///       times.
962         ///    </para>
963         /// </devdoc>
964         public MatchCollection Matches(String input, int startat) {
965
966             if (input == null)
967                 throw new ArgumentNullException("input");
968
969             return new MatchCollection(this, input, 0, input.Length, startat);
970         }
971
972         /*
973          * Static version of simple Replace call
974          */
975         /// <devdoc>
976         ///    <para>
977         ///       Replaces 
978         ///          all occurrences of the pattern with the <paramref name="replacement"/> pattern, starting at
979         ///          the first character in the input string. 
980         ///       </para>
981         ///    </devdoc>
982         public static String Replace(String input, String pattern, String replacement) {
983             return Replace(input, pattern, replacement, RegexOptions.None, DefaultMatchTimeout);
984         }
985
986         /*
987          * Static version of simple Replace call
988          */
989         /// <devdoc>
990         ///    <para>
991         ///       Replaces all occurrences of 
992         ///          the <paramref name="pattern "/>with the <paramref name="replacement "/>
993         ///          pattern, starting at the first character in the input string. 
994         ///       </para>
995         ///    </devdoc>
996         public static String Replace(String input, String pattern, String replacement, RegexOptions options) {
997             return Replace(input, pattern, replacement, options, DefaultMatchTimeout);
998         }
999
1000         #if !SILVERLIGHT || FEATURE_NETCORE
1001         public
1002         #else
1003         private
1004         #endif
1005                static String Replace(String input, String pattern, String replacement, RegexOptions options, TimeSpan matchTimeout) {
1006             return new Regex(pattern, options, matchTimeout, true).Replace(input, replacement);
1007         }
1008
1009         /*
1010          * Does the replacement
1011          */
1012         /// <devdoc>
1013         ///    <para>
1014         ///       Replaces all occurrences of 
1015         ///          the <paramref name="pattern "/> with the <paramref name="replacement"/> pattern, starting at the
1016         ///          first character in the input string, using the previous patten. 
1017         ///       </para>
1018         ///    </devdoc>
1019         public String Replace(String input, String replacement) {
1020
1021             if (input == null)
1022                 throw new ArgumentNullException("input");
1023
1024             return Replace(input, replacement, -1, UseOptionR() ? input.Length : 0);
1025         }
1026
1027         /*
1028          * Does the replacement
1029          */
1030         /// <devdoc>
1031         ///    <para>
1032         ///    Replaces all occurrences of the (previously defined) <paramref name="pattern "/>with the 
1033         ///    <paramref name="replacement"/> pattern, starting at the first character in the input string. 
1034         /// </para>
1035         /// </devdoc>
1036         public String Replace(String input, String replacement, int count) {
1037
1038             if (input == null)
1039                 throw new ArgumentNullException("input");
1040
1041             return Replace(input, replacement, count, UseOptionR() ? input.Length : 0);
1042         }
1043
1044         /*
1045          * Does the replacement
1046          */
1047         /// <devdoc>
1048         ///    <para>
1049         ///    Replaces all occurrences of the <paramref name="pattern "/>with the recent 
1050         ///    <paramref name="replacement"/> pattern, starting at the character position 
1051         ///    <paramref name="startat."/>
1052         /// </para>
1053         /// </devdoc>
1054         public String Replace(String input, String replacement, int count, int startat) {
1055
1056             if (input == null)
1057                 throw new ArgumentNullException("input");
1058
1059             if (replacement == null)
1060                 throw new ArgumentNullException("replacement");
1061
1062             // a little code to grab a cached parsed replacement object
1063             RegexReplacement repl = (RegexReplacement) replref.Get();
1064
1065             if (repl == null || !repl.Pattern.Equals(replacement)) {
1066                 repl = RegexParser.ParseReplacement(replacement, caps, capsize, capnames, this.roptions);
1067                 replref.Cache(repl);
1068             }
1069
1070             return repl.Replace(this, input, count, startat);
1071         }
1072
1073         /*
1074          * Static version of simple Replace call
1075          */
1076         /// <devdoc>
1077         ///    <para>
1078         ///    Replaces all occurrences of the <paramref name="pattern "/>with the 
1079         ///    <paramref name="replacement"/> pattern 
1080         ///    <paramref name="."/>
1081         /// </para>
1082         /// </devdoc>
1083         public static String Replace(String input, String pattern, MatchEvaluator evaluator) {
1084             return Replace(input, pattern, evaluator, RegexOptions.None, DefaultMatchTimeout);
1085         }
1086
1087         /*
1088          * Static version of simple Replace call
1089          */
1090         /// <devdoc>
1091         ///    <para>
1092         ///    Replaces all occurrences of the <paramref name="pattern "/>with the recent 
1093         ///    <paramref name="replacement"/> pattern, starting at the first character<paramref name="."/>
1094         /// </para>
1095         /// </devdoc>
1096         public static String Replace(String input, String pattern, MatchEvaluator evaluator, RegexOptions options) {
1097             return Replace(input, pattern, evaluator, options, DefaultMatchTimeout);
1098         }
1099
1100         #if !SILVERLIGHT || FEATURE_NETCORE
1101         public
1102         #else
1103         private
1104         #endif
1105                static String Replace(String input, String pattern, MatchEvaluator evaluator, RegexOptions options, TimeSpan matchTimeout) {
1106             return new Regex(pattern, options, matchTimeout, true).Replace(input, evaluator);
1107         }
1108
1109         /*
1110          * Does the replacement
1111          */
1112         /// <devdoc>
1113         ///    <para>
1114         ///    Replaces all occurrences of the <paramref name="pattern "/>with the recent 
1115         ///    <paramref name="replacement"/> pattern, starting at the first character 
1116         ///    position<paramref name="."/>
1117         /// </para>
1118         /// </devdoc>
1119         public String Replace(String input, MatchEvaluator evaluator) {
1120
1121             if (input == null)
1122                 throw new ArgumentNullException("input");
1123
1124             return Replace(input, evaluator, -1, UseOptionR() ? input.Length : 0);
1125         }
1126
1127         /*
1128          * Does the replacement
1129          */
1130         /// <devdoc>
1131         ///    <para>
1132         ///    Replaces all occurrences of the <paramref name="pattern "/>with the recent 
1133         ///    <paramref name="replacement"/> pattern, starting at the first character 
1134         ///    position<paramref name="."/>
1135         /// </para>
1136         /// </devdoc>
1137         public String Replace(String input, MatchEvaluator evaluator, int count) {
1138
1139             if (input == null)
1140                 throw new ArgumentNullException("input");
1141
1142             return Replace(input, evaluator, count, UseOptionR() ? input.Length : 0);
1143         }
1144
1145         /*
1146          * Does the replacement
1147          */
1148         /// <devdoc>
1149         ///    <para>
1150         ///    Replaces all occurrences of the (previouly defined) <paramref name="pattern "/>with 
1151         ///       the recent <paramref name="replacement"/> pattern, starting at the character
1152         ///    position<paramref name=" startat."/> 
1153         /// </para>
1154         /// </devdoc>
1155         public String Replace(String input, MatchEvaluator evaluator, int count, int startat) {
1156
1157             if (input == null)
1158                 throw new ArgumentNullException("input");
1159
1160             return RegexReplacement.Replace(evaluator, this, input, count, startat);
1161         }
1162
1163         /*
1164          * Static version of simple Split call
1165          */
1166         ///    <devdoc>
1167         ///       <para>
1168         ///          Splits the <paramref name="input "/>string at the position defined
1169         ///          by <paramref name="pattern"/>.
1170         ///       </para>
1171         ///    </devdoc>
1172         public static String[] Split(String input, String pattern) {
1173             return Split(input, pattern, RegexOptions.None, DefaultMatchTimeout);
1174         }
1175
1176         /*
1177          * Static version of simple Split call
1178          */
1179         /// <devdoc>
1180         ///    <para>
1181         ///       Splits the <paramref name="input "/>string at the position defined by <paramref name="pattern"/>.
1182         ///    </para>
1183         /// </devdoc>
1184         public static String[] Split(String input, String pattern, RegexOptions options) {
1185             return Split(input, pattern, options, DefaultMatchTimeout);
1186         }
1187
1188         #if !SILVERLIGHT || FEATURE_NETCORE
1189         public
1190         #else
1191         private
1192         #endif
1193                static String[] Split(String input, String pattern, RegexOptions options, TimeSpan matchTimeout) {
1194             return new Regex(pattern, options, matchTimeout, true).Split(input);
1195         }
1196
1197         /*
1198          * Does a split
1199          */
1200         /// <devdoc>
1201         ///    <para>
1202         ///       Splits the <paramref name="input "/>string at the position defined by
1203         ///       a previous <paramref name="pattern"/>
1204         ///       .
1205         ///    </para>
1206         /// </devdoc>
1207         public String[] Split(String input) {
1208
1209             if (input == null)
1210                 throw new ArgumentNullException("input");
1211
1212             return Split(input, 0, UseOptionR() ? input.Length : 0);
1213         }
1214
1215         /*
1216          * Does a split
1217          */
1218         /// <devdoc>
1219         ///    <para>
1220         ///       Splits the <paramref name="input "/>string at the position defined by a previous
1221         ///    <paramref name="pattern"/> . 
1222         ///    </para>
1223         /// </devdoc>
1224         public String[] Split(String input, int count) {
1225
1226             if (input == null)
1227                 throw new ArgumentNullException("input");
1228
1229             return RegexReplacement.Split(this, input, count, UseOptionR() ? input.Length : 0);
1230         }
1231
1232         /*
1233          * Does a split
1234          */
1235         /// <devdoc>
1236         ///    <para>
1237         ///       Splits the <paramref name="input "/>string at the position defined by a previous
1238         ///    <paramref name="pattern"/> . 
1239         ///    </para>
1240         /// </devdoc>
1241         public String[] Split(String input, int count, int startat) {
1242             if (input==null)
1243                 throw new ArgumentNullException("input");
1244
1245             return RegexReplacement.Split(this, input, count, startat);
1246         }
1247
1248
1249         
1250 #if !(SILVERLIGHT || FULL_AOT_RUNTIME)
1251         /// <devdoc>
1252         /// </devdoc>
1253 #if MONO_FEATURE_CAS
1254         [HostProtection(MayLeakOnAbort=true)]
1255 #endif
1256         [ResourceExposure(ResourceScope.Machine)] // The AssemblyName is interesting.
1257         [ResourceConsumption(ResourceScope.Machine)]
1258         [SuppressMessage("Microsoft.Naming","CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId="assemblyname", Justification="[....]: already shipped since v1 - can't fix without causing a breaking change")]
1259         public static void CompileToAssembly(RegexCompilationInfo[] regexinfos, AssemblyName assemblyname) {
1260         
1261             CompileToAssemblyInternal(regexinfos, assemblyname, null, null);
1262         }
1263
1264         /// <devdoc>
1265         /// </devdoc>
1266 #if MONO_FEATURE_CAS
1267         [HostProtection(MayLeakOnAbort=true)]
1268 #endif
1269         [ResourceExposure(ResourceScope.Machine)] // The AssemblyName is interesting.
1270         [ResourceConsumption(ResourceScope.Machine)]
1271         [SuppressMessage("Microsoft.Naming","CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId="assemblyname", Justification="[....]: already shipped since v1 - can't fix without causing a breaking change")]
1272         public static void CompileToAssembly(RegexCompilationInfo[] regexinfos, AssemblyName assemblyname, CustomAttributeBuilder[] attributes) {
1273             CompileToAssemblyInternal(regexinfos, assemblyname, attributes, null);
1274         }
1275
1276 #if MONO_FEATURE_CAS
1277         [HostProtection(MayLeakOnAbort=true)]
1278 #endif
1279         [ResourceExposure(ResourceScope.Machine)]
1280         [ResourceConsumption(ResourceScope.Machine)]
1281         [SuppressMessage("Microsoft.Naming","CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId="assemblyname", Justification="[....]: already shipped since v1 - can't fix without causing a breaking change")]
1282         public static void CompileToAssembly(RegexCompilationInfo[] regexinfos, AssemblyName assemblyname, CustomAttributeBuilder[] attributes, String resourceFile) {
1283             CompileToAssemblyInternal(regexinfos, assemblyname, attributes, resourceFile);
1284         }
1285
1286         [ResourceExposure(ResourceScope.Machine)]  // AssemblyName & resourceFile
1287         [ResourceConsumption(ResourceScope.Machine)]
1288         private static void CompileToAssemblyInternal (RegexCompilationInfo[] regexinfos, AssemblyName assemblyname, CustomAttributeBuilder[] attributes, String resourceFile) {
1289             if (assemblyname == null)
1290                 throw new ArgumentNullException("assemblyname");
1291
1292             if (regexinfos == null)
1293                 throw new ArgumentNullException("regexinfos");
1294         
1295             RegexCompiler.CompileToAssembly(regexinfos, assemblyname, attributes, resourceFile);
1296         }
1297         
1298 #endif
1299
1300         /// <devdoc>
1301         /// </devdoc>
1302         protected void InitializeReferences() {
1303             if (refsInitialized)
1304                 throw new NotSupportedException(SR.GetString(SR.OnlyAllowedOnce));
1305             
1306             refsInitialized = true;
1307             runnerref  = new ExclusiveReference();
1308             replref    = new SharedReference();
1309         }
1310
1311         
1312         /*
1313          * Internal worker called by all the public APIs
1314          */
1315         internal Match Run(bool quick, int prevlen, String input, int beginning, int length, int startat) {
1316             Match match;
1317             RegexRunner runner = null;
1318
1319             if (startat < 0 || startat > input.Length)
1320                 throw new ArgumentOutOfRangeException("start", SR.GetString(SR.BeginIndexNotNegative));
1321
1322             if (length < 0 || length > input.Length)
1323                 throw new ArgumentOutOfRangeException("length", SR.GetString(SR.LengthNotNegative));
1324
1325             // There may be a cached runner; grab ownership of it if we can.
1326
1327             runner = (RegexRunner)runnerref.Get();
1328
1329             // Create a RegexRunner instance if we need to
1330
1331             if (runner == null) {
1332                 // Use the compiled RegexRunner factory if the code was compiled to MSIL
1333
1334                 if (factory != null)
1335                     runner = factory.CreateInstance();
1336                 else
1337                     runner = new RegexInterpreter(code, UseOptionInvariant() ? CultureInfo.InvariantCulture : CultureInfo.CurrentCulture);
1338             }
1339
1340             try {
1341                 // Do the scan starting at the requested position            
1342                 match = runner.Scan(this, input, beginning, beginning + length, startat, prevlen, quick, internalMatchTimeout);
1343             } finally {
1344                 // Release or fill the cache slot
1345                 runnerref.Release(runner);
1346             }
1347
1348 #if DBG
1349             if (Debug && match != null)
1350                 match.Dump();
1351 #endif
1352             return match;
1353         }
1354
1355         /*
1356          * Find code cache based on options+pattern
1357          */
1358         private static CachedCodeEntry LookupCachedAndUpdate(String key) {
1359             lock (livecode) {
1360                 for (LinkedListNode<CachedCodeEntry> current = livecode.First; current != null; current = current.Next) {
1361                     if (current.Value._key == key) {
1362                         // If we find an entry in the cache, move it to the head at the same time. 
1363                         livecode.Remove(current);
1364                         livecode.AddFirst(current);
1365                         return current.Value;
1366                     }
1367                 }
1368             }
1369
1370             return null;
1371         }
1372
1373         /*
1374          * Add current code to the cache
1375          */
1376         private CachedCodeEntry CacheCode(String key) {
1377             CachedCodeEntry newcached = null;
1378
1379             lock (livecode) {
1380                 // first look for it in the cache and move it to the head
1381                 for (LinkedListNode<CachedCodeEntry> current = livecode.First; current != null; current = current.Next) {
1382                     if (current.Value._key == key) {
1383                         livecode.Remove(current);
1384                         livecode.AddFirst(current);
1385                         return current.Value;
1386                     }
1387                 }
1388
1389                 // it wasn't in the cache, so we'll add a new one.  Shortcut out for the case where cacheSize is zero.
1390                 if (cacheSize != 0) {
1391                     newcached = new CachedCodeEntry(key, capnames, capslist, code, caps, capsize, runnerref, replref);
1392                     livecode.AddFirst(newcached);
1393                     if (livecode.Count > cacheSize)
1394                         livecode.RemoveLast();
1395                 }
1396             }
1397
1398             return newcached;
1399         }
1400
1401 #if !SILVERLIGHT
1402         /*
1403          * True if the O option was set
1404          */
1405         /// <internalonly/>
1406         /// <devdoc>
1407         /// </devdoc>
1408         protected bool UseOptionC() {
1409 #if FULL_AOT_RUNTIME
1410             return false;
1411 #else
1412
1413 #if MONO
1414             /* Mono: Set to false until we investigate  https://bugzilla.xamarin.com/show_bug.cgi?id=25671 */
1415             return false;
1416 #else
1417             return(roptions & RegexOptions.Compiled) != 0;
1418 #endif
1419 #endif
1420         }
1421 #endif
1422
1423         /*
1424          * True if the L option was set
1425          */
1426         /// <internalonly/>
1427         /// <devdoc>
1428         /// </devdoc>
1429         protected bool UseOptionR() {
1430             return(roptions & RegexOptions.RightToLeft) != 0;
1431         }
1432
1433         internal bool UseOptionInvariant() {
1434             return(roptions & RegexOptions.CultureInvariant) != 0;
1435         }
1436             
1437
1438 #if DBG
1439         /*
1440          * True if the regex has debugging enabled
1441          */
1442         /// <internalonly/>
1443         /// <devdoc>
1444         /// </devdoc>
1445         internal bool Debug {
1446             get {
1447                 return(roptions & RegexOptions.Debug) != 0;
1448             }
1449         }
1450
1451 #endif
1452     }
1453
1454
1455     /*
1456      * Callback class
1457      */
1458     /// <devdoc>
1459     /// </devdoc>
1460 #if !SILVERLIGHT
1461     [ Serializable() ] 
1462 #endif
1463     public delegate String MatchEvaluator(Match match);
1464
1465
1466     /*
1467      * Used to cache byte codes or compiled factories
1468      */
1469     internal sealed class CachedCodeEntry {
1470         internal string _key;
1471         internal RegexCode _code;
1472 #if SILVERLIGHT
1473         internal Dictionary<Int32, Int32> _caps;
1474         internal Dictionary<String, Int32> _capnames;
1475 #else
1476         internal Hashtable _caps;
1477         internal Hashtable _capnames;
1478 #endif
1479         internal String[]  _capslist;
1480         internal int       _capsize;
1481         internal RegexRunnerFactory _factory;
1482         internal ExclusiveReference _runnerref;
1483         internal SharedReference _replref;
1484
1485 #if SILVERLIGHT
1486         internal CachedCodeEntry(string key, Dictionary<String, Int32> capnames, String[] capslist, RegexCode code, Dictionary<Int32, Int32> caps, int capsize, ExclusiveReference runner, SharedReference repl)
1487 #else
1488         internal CachedCodeEntry(string key, Hashtable capnames, String[] capslist, RegexCode code, Hashtable caps, int capsize, ExclusiveReference runner, SharedReference repl)
1489 #endif
1490         {
1491
1492             _key        = key;
1493             _capnames   = capnames;
1494             _capslist   = capslist;
1495
1496             _code       = code;
1497             _caps       = caps;
1498             _capsize    = capsize;
1499
1500             _runnerref     = runner;
1501             _replref       = repl;
1502         }
1503
1504 #if !SILVERLIGHT
1505         internal void AddCompiled(RegexRunnerFactory factory) {
1506             _factory = factory;
1507             _code = null;
1508         }
1509 #endif
1510     }
1511
1512     /*
1513      * Used to cache one exclusive runner reference
1514      */
1515     internal sealed class ExclusiveReference {
1516         RegexRunner _ref;
1517         Object _obj;
1518         int _locked;
1519
1520         /*
1521          * Return an object and grab an exclusive lock.
1522          *
1523          * If the exclusive lock can't be obtained, null is returned;
1524          * if the object can't be returned, the lock is released.
1525          *
1526          */
1527         internal Object Get() {
1528             // try to obtain the lock
1529
1530             if (0 == Interlocked.Exchange(ref _locked, 1)) {
1531                 // grab reference
1532
1533                    
1534                 Object obj = _ref;
1535
1536                 // release the lock and return null if no reference
1537
1538                 if (obj == null) {
1539                     _locked = 0;
1540                     return null;
1541                 }
1542
1543                 // remember the reference and keep the lock
1544
1545                 _obj = obj;
1546                 return obj;
1547             }
1548
1549             return null;
1550         }
1551
1552         /*
1553          * Release an object back to the cache
1554          *
1555          * If the object is the one that's under lock, the lock
1556          * is released.
1557          *
1558          * If there is no cached object, then the lock is obtained
1559          * and the object is placed in the cache.
1560          *
1561          */
1562         internal void Release(Object obj) {
1563             if (obj == null)
1564                 throw new ArgumentNullException("obj");
1565
1566             // if this reference owns the lock, release it
1567
1568             if (_obj == obj) {
1569                 _obj = null;
1570                 _locked = 0;
1571                 return;
1572             }
1573
1574             // if no reference owns the lock, try to cache this reference
1575
1576             if (_obj == null) {
1577                 // try to obtain the lock
1578
1579                 if (0 == Interlocked.Exchange(ref _locked, 1)) {
1580                     // if there's really no reference, cache this reference
1581
1582                     if (_ref == null)
1583                         _ref = (RegexRunner) obj;
1584
1585                     // release the lock
1586
1587                     _locked = 0;
1588                     return;
1589                 }
1590             }
1591         }
1592     }
1593
1594     /*
1595      * Used to cache a weak reference in a threadsafe way
1596      */
1597     internal sealed class SharedReference {
1598         WeakReference _ref = new WeakReference(null);
1599         int _locked;
1600
1601         /*
1602          * Return an object from a weakref, protected by a lock.
1603          *
1604          * If the exclusive lock can't be obtained, null is returned;
1605          *
1606          * Note that _ref.Target is referenced only under the protection
1607          * of the lock. (Is this necessary?)
1608          */
1609         internal  Object Get() {
1610             if (0 == Interlocked.Exchange(ref _locked, 1)) {
1611                 Object obj = _ref.Target;
1612                 _locked = 0;
1613                 return obj;
1614             }
1615
1616             return null;
1617         }
1618
1619         /*
1620          * Suggest an object into a weakref, protected by a lock.
1621          *
1622          * Note that _ref.Target is referenced only under the protection
1623          * of the lock. (Is this necessary?)
1624          */
1625         internal void Cache(Object obj) {
1626             if (0 == Interlocked.Exchange(ref _locked, 1)) {
1627                 _ref.Target = obj;
1628                 _locked = 0;
1629             }
1630         }
1631     }
1632
1633 }