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