Added tests for Task.WhenAll w/ empty list
[mono.git] / mcs / class / System / System.Text.RegularExpressions / Regex.cs
1 //
2 // assembly:    System
3 // namespace:   System.Text.RegularExpressions
4 // file:        regex.cs
5 //
6 // author:      Dan Lewis (dlewis@gmx.co.uk)
7 //              (c) 2002
8
9 //
10 // Permission is hereby granted, free of charge, to any person obtaining
11 // a copy of this software and associated documentation files (the
12 // "Software"), to deal in the Software without restriction, including
13 // without limitation the rights to use, copy, modify, merge, publish,
14 // distribute, sublicense, and/or sell copies of the Software, and to
15 // permit persons to whom the Software is furnished to do so, subject to
16 // the following conditions:
17 // 
18 // The above copyright notice and this permission notice shall be
19 // included in all copies or substantial portions of the Software.
20 // 
21 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
22 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
25 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
26 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
27 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28 //
29
30 using System;
31 using System.Text;
32 using System.Collections;
33 using System.Reflection;
34 using System.Reflection.Emit;
35 using System.Runtime.Serialization;
36
37 using RegularExpression = System.Text.RegularExpressions.Syntax.RegularExpression;
38 using Parser = System.Text.RegularExpressions.Syntax.Parser;
39
40 using System.Diagnostics;
41
42
43 namespace System.Text.RegularExpressions {
44         
45         [Serializable]
46         public partial class Regex : ISerializable {
47
48 #if !TARGET_JVM && !FULL_AOT_RUNTIME
49                 [MonoTODO]
50                 public static void CompileToAssembly (RegexCompilationInfo [] regexes, AssemblyName aname)
51                 {
52                         Regex.CompileToAssembly(regexes, aname, new CustomAttributeBuilder [] {}, null);
53                 }
54
55                 [MonoTODO]
56                 public static void CompileToAssembly (RegexCompilationInfo [] regexes, AssemblyName aname,
57                                                       CustomAttributeBuilder [] attribs)
58                 {
59                         Regex.CompileToAssembly(regexes, aname, attribs, null);
60                 }
61
62                 [MonoTODO]
63                 public static void CompileToAssembly (RegexCompilationInfo [] regexes, AssemblyName aname,
64                                                       CustomAttributeBuilder [] attribs, string resourceFile)
65                 {
66                         throw new NotImplementedException ();
67                         // TODO : Make use of attribs and resourceFile parameters
68                         /*
69                         AssemblyBuilder asmBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly (aname, AssemblyBuilderAccess.RunAndSave);
70                         ModuleBuilder modBuilder = asmBuilder.DefineDynamicModule("InnerRegexModule",aname.Name);
71                         Parser psr = new Parser ();     
72                         
73                         System.Console.WriteLine("CompileToAssembly");
74                                
75                         for(int i=0; i < regexes.Length; i++)
76                                 {
77                                         System.Console.WriteLine("Compiling expression :" + regexes[i].Pattern);
78                                         RegularExpression re = psr.ParseRegularExpression (regexes[i].Pattern, regexes[i].Options);
79                                         
80                                         // compile
81                                                                                 
82                                         CILCompiler cmp = new CILCompiler (modBuilder, i);
83                                         bool reverse = (regexes[i].Options & RegexOptions.RightToLeft) !=0;
84                                         re.Compile (cmp, reverse);
85                                         cmp.Close();
86                                         
87                                 }
88                        
89
90                         // Define a runtime class with specified name and attributes.
91                         TypeBuilder builder = modBuilder.DefineType("ITest");
92                         builder.CreateType();
93                         asmBuilder.Save(aname.Name);
94                         */
95                 }
96 #endif
97                 
98                 public static string Escape (string str)
99                 {
100                         if (str == null)
101                                 throw new ArgumentNullException ("str");
102                         return Parser.Escape (str);
103                 }
104
105                 public static string Unescape (string str)
106                 {
107                         if (str == null)
108                                 throw new ArgumentNullException ("str");
109                         return Parser.Unescape (str);
110                 }
111
112                 public static bool IsMatch (string input, string pattern)
113                 {
114                         return IsMatch (input, pattern, RegexOptions.None);
115                 }
116
117                 public static bool IsMatch (string input, string pattern, RegexOptions options)
118                 {
119                         Regex re = new Regex (pattern, options);
120                         return re.IsMatch (input);
121                 }
122
123                 public static Match Match (string input, string pattern)
124                 {
125                         return Regex.Match (input, pattern, RegexOptions.None);
126                 }
127
128                 public static Match Match (string input, string pattern, RegexOptions options)
129                 {
130                         Regex re = new Regex (pattern, options);
131                         return re.Match (input);
132                 }
133
134                 public static MatchCollection Matches (string input, string pattern)
135                 {
136                         return Matches (input, pattern, RegexOptions.None);
137                 }
138
139                 public static MatchCollection Matches (string input, string pattern, RegexOptions options)
140                 {
141                         Regex re = new Regex (pattern, options);
142                         return re.Matches (input);
143                 }
144
145                 public static string Replace (string input, string pattern, MatchEvaluator evaluator)
146                 {
147                         return Regex.Replace (input, pattern, evaluator, RegexOptions.None);
148                 }
149
150                 public static string Replace (string input, string pattern, MatchEvaluator evaluator,
151                                               RegexOptions options)
152                 {
153                         Regex re = new Regex (pattern, options);
154                         return re.Replace (input, evaluator);
155                 }
156
157                 public static string Replace (string input, string pattern, string replacement)
158                 {
159                         return Regex.Replace (input, pattern, replacement, RegexOptions.None);
160                 }
161
162                 public static string Replace (string input, string pattern, string replacement,
163                                               RegexOptions options)
164                 {
165                         Regex re = new Regex (pattern, options);
166                         return re.Replace (input, replacement);
167                 }
168
169                 public static string [] Split (string input, string pattern)
170                 {
171                         return Regex.Split (input, pattern, RegexOptions.None);
172                 }
173
174                 public static string [] Split (string input, string pattern, RegexOptions options)
175                 {
176                         Regex re = new Regex (pattern, options);
177                         return re.Split (input);
178                 }
179
180                 static FactoryCache cache = new FactoryCache (15);
181                 public static int CacheSize {
182                         get { return cache.Capacity; }
183                         set {
184                                 if (value < 0)
185                                         throw new ArgumentOutOfRangeException ("CacheSize");
186
187                                 cache.Capacity = value; 
188                         }
189                 }
190
191                 // private
192
193
194                 // constructors
195
196                 // This constructor is used by compiled regular expressions that are
197                 // classes derived from Regex class. No initialization required.
198                 protected Regex ()
199                 {
200                 }
201
202                 public Regex (string pattern) : this (pattern, RegexOptions.None)
203                 {
204                 }
205
206                 public Regex (string pattern, RegexOptions options)
207                 {
208                         if (pattern == null)
209                                 throw new ArgumentNullException ("pattern");
210                         validate_options (options);
211                         this.pattern = pattern;
212                         this.roptions = options;
213                         Init ();
214                 }
215                 
216 #if NET_4_5
217                 [MonoTODO ("Timeouts are ignored.")]
218                 public Regex (string pattern, RegexOptions options, TimeSpan matchTimeout)
219                         : this (pattern, options)
220                 {
221                         MatchTimeout = matchTimeout;
222                 }
223                 
224                 [MonoTODO ("Timeouts are ignored.")]
225                 public TimeSpan MatchTimeout {
226                         get;
227                         private set;
228                 }
229                 
230                 [MonoTODO ("Timeouts are ignored.")]
231                 public static bool IsMatch (
232                         string input, string pattern, RegexOptions options, TimeSpan matchTimeout)
233                 {
234                         return IsMatch (input, pattern, options);
235                 }
236                 
237                 [MonoTODO ("Timeouts are ignored.")]
238                 public static Match Match (
239                         string input, string pattern, RegexOptions options, TimeSpan matchTimeout)
240                 {
241                         return Match (input, pattern, options);
242                 }
243                 
244                 [MonoTODO ("Timeouts are ignored.")]
245                 public static MatchCollection Matches (
246                         string input, string pattern, RegexOptions options, TimeSpan matchTimeout)
247                 {
248                         return Matches (input, pattern, options, matchTimeout);
249                 }
250                 
251                 [MonoTODO ("Timeouts are ignored.")]
252                 public static string Replace (
253                         string input, string pattern, string replacement, RegexOptions options,
254                         TimeSpan matchTimeout)
255                 {
256                         return Replace (input, pattern, replacement, options);
257                 }
258                 
259                 [MonoTODO ("Timeouts are ignored.")]
260                 public static string Replace (
261                         string input, string pattern, MatchEvaluator evaluator, RegexOptions options,
262                         TimeSpan matchTimeout)
263                 {
264                         return Replace (input, pattern, evaluator, options);
265                 }
266                 
267                 [MonoTODO ("Timeouts are ignored.")]
268                 public static string[] Split (
269                         string input, string pattern, RegexOptions options, TimeSpan matchTimeout)
270                 {
271                         return Split (input, pattern, options);
272                 }
273 #endif
274
275                 static void validate_options (RegexOptions options)
276                 {
277                         const RegexOptions allopts =
278                                 RegexOptions.None |
279                                 RegexOptions.IgnoreCase |
280                                 RegexOptions.Multiline |
281                                 RegexOptions.ExplicitCapture |
282 #if MOBILE || !NET_2_1
283                                 RegexOptions.Compiled |
284 #endif
285                                 RegexOptions.Singleline |
286                                 RegexOptions.IgnorePatternWhitespace |
287                                 RegexOptions.RightToLeft |
288                                 RegexOptions.ECMAScript |
289                                 RegexOptions.CultureInvariant;
290
291                         const RegexOptions ecmaopts =
292                                 RegexOptions.IgnoreCase |
293                                 RegexOptions.Multiline |
294 #if MOBILE || !NET_2_1
295                                 RegexOptions.Compiled |
296 #endif
297                                 RegexOptions.ECMAScript;
298
299                         if ((options & ~allopts) != 0)
300                                 throw new ArgumentOutOfRangeException ("options");
301                         if ((options & RegexOptions.ECMAScript) != 0 && (options & ~ecmaopts) != 0)
302                                 throw new ArgumentOutOfRangeException ("options");
303                 }
304
305 #if !TARGET_JVM
306                 private void Init ()
307                 {
308                         this.machineFactory = cache.Lookup (this.pattern, this.roptions);
309
310                         if (this.machineFactory == null) {
311                                 InitNewRegex();
312                         } else {
313                                 this.group_count = this.machineFactory.GroupCount;
314                                 this.gap = this.machineFactory.Gap;
315                                 this.mapping = this.machineFactory.Mapping;
316                                 this.group_names = this.machineFactory.NamesMapping;
317                         }
318                 }
319 #endif
320
321                 private void InitNewRegex () 
322                 {
323                         this.machineFactory = CreateMachineFactory (this.pattern, this.roptions);
324                         cache.Add (this.pattern, this.roptions, this.machineFactory);
325                         this.group_count = machineFactory.GroupCount;
326                         this.gap = this.machineFactory.Gap;
327                         this.mapping = machineFactory.Mapping;
328                         this.group_names = this.machineFactory.NamesMapping;
329                 }
330
331 #if !NET_2_1
332                 // The new rx engine seems to be working now, but
333                 // potential problems are being tracked down here:
334                 // https://bugzilla.novell.com/show_bug.cgi?id=470827
335                 static readonly bool old_rx =
336                         Environment.GetEnvironmentVariable ("MONO_NEW_RX") == null;
337 #endif
338
339                 private static IMachineFactory CreateMachineFactory (string pattern, RegexOptions options) 
340                 {
341                         Parser psr = new Parser ();
342                         RegularExpression re = psr.ParseRegularExpression (pattern, options);
343
344 #if NET_2_1
345                         ICompiler cmp = new PatternCompiler ();
346 #else
347                         ICompiler cmp;
348                         if (!old_rx) {
349                                 if ((options & RegexOptions.Compiled) != 0)
350                                         cmp = new CILCompiler ();
351                                 else
352                                         cmp = new RxCompiler ();
353                         } else {
354                                 cmp = new PatternCompiler ();
355                         }
356 #endif
357
358                         re.Compile (cmp, (options & RegexOptions.RightToLeft) != 0);
359
360                         IMachineFactory machineFactory = cmp.GetMachineFactory ();
361                         Hashtable mapping = new Hashtable ();
362                         machineFactory.Gap = psr.GetMapping (mapping);
363                         machineFactory.Mapping = mapping;
364                         machineFactory.NamesMapping = GetGroupNamesArray (machineFactory.GroupCount, machineFactory.Mapping);
365
366                         return machineFactory;
367                 }
368
369                 protected Regex (SerializationInfo info, StreamingContext context) :
370                         this (info.GetString ("pattern"), 
371                               (RegexOptions) info.GetValue ("options", typeof (RegexOptions)))
372                 {
373                 }
374
375                 // public instance properties
376                 
377                 public RegexOptions Options {
378                         get { return roptions; }
379                 }
380
381                 public bool RightToLeft {
382                         get { return (roptions & RegexOptions.RightToLeft) != 0; }
383                 }
384
385                 // public instance methods
386                 
387                 public string [] GetGroupNames ()
388                 {
389                         string [] names = new string [1 + group_count];
390                         Array.Copy (group_names, names, 1 + group_count);
391                         return names;
392                 }
393
394                 public int [] GetGroupNumbers ()
395                 {
396                         int [] numbers = new int [1 + group_count];
397                         Array.Copy (GroupNumbers, numbers, 1 + group_count);
398                         return numbers;
399                 }
400
401                 public string GroupNameFromNumber (int i)
402                 {
403                         i = GetGroupIndex (i);
404                         if (i < 0)
405                                 return "";
406
407                         return group_names [i];
408                 }
409
410                 public int GroupNumberFromName (string name)
411                 {
412                         if (!mapping.Contains (name))
413                                 return -1;
414                         int i = (int) mapping [name];
415                         if (i >= gap)
416                                 i = Int32.Parse (name);
417                         return i;
418                 }
419
420                 internal int GetGroupIndex (int number)
421                 {
422                         if (number < gap)
423                                 return number;
424                         if (gap > group_count)
425                                 return -1;
426                         return Array.BinarySearch (GroupNumbers, gap, group_count - gap + 1, number);
427                 }
428
429                 int default_startat (string input)
430                 {
431                         return (RightToLeft && input != null) ? input.Length : 0;
432                 }
433
434                 // match methods
435                 
436                 public bool IsMatch (string input)
437                 {
438                         return IsMatch (input, default_startat (input));
439                 }
440
441                 public bool IsMatch (string input, int startat)
442                 {
443                         return Match (input, startat).Success;
444                 }
445
446                 public Match Match (string input)
447                 {
448                         return Match (input, default_startat (input));
449                 }
450
451                 public Match Match (string input, int startat)
452                 {
453                         if (input == null)
454                                 throw new ArgumentNullException ("input");
455                         if (startat < 0 || startat > input.Length)
456                                 throw new ArgumentOutOfRangeException ("startat");
457                         return CreateMachine ().Scan (this, input, startat, input.Length);
458                 }
459
460                 public Match Match (string input, int beginning, int length)
461                 {
462                         if (input == null)
463                                 throw new ArgumentNullException ("input");
464                         if (beginning < 0 || beginning > input.Length)
465                                 throw new ArgumentOutOfRangeException ("beginning");
466                         if (length < 0 || length > input.Length - beginning)
467                                 throw new ArgumentOutOfRangeException ("length");
468                         return CreateMachine ().Scan (this, input, beginning, beginning + length);
469                 }
470
471                 public MatchCollection Matches (string input)
472                 {
473                         return Matches (input, default_startat (input));
474                 }
475
476                 public MatchCollection Matches (string input, int startat)
477                 {
478                         Match m = Match (input, startat);
479                         return new MatchCollection (m);
480                 }
481
482                 // replace methods
483
484                 public string Replace (string input, MatchEvaluator evaluator)
485                 {
486                         return Replace (input, evaluator, Int32.MaxValue, default_startat (input));
487                 }
488
489                 public string Replace (string input, MatchEvaluator evaluator, int count)
490                 {
491                         return Replace (input, evaluator, count, default_startat (input));
492                 }
493
494                 class Adapter {
495                         MatchEvaluator ev;
496                         public Adapter (MatchEvaluator ev) { this.ev = ev; }
497                         public void Evaluate (Match m, StringBuilder sb) { sb.Append (ev (m)); }
498                 }
499
500                 public string Replace (string input, MatchEvaluator evaluator, int count, int startat)
501                 {
502                         if (input == null)
503                                 throw new ArgumentNullException ("input");
504                         if (evaluator == null)
505                                 throw new ArgumentNullException ("evaluator");
506                         if (count < -1)
507                                 throw new ArgumentOutOfRangeException ("count");
508                         if (startat < 0 || startat > input.Length)
509                                 throw new ArgumentOutOfRangeException ("startat");
510
511                         BaseMachine m = (BaseMachine)CreateMachine ();
512
513                         if (RightToLeft)
514                                 return m.RTLReplace (this, input, evaluator, count, startat);
515
516                         // NOTE: If this is a cause of a lot of allocations, we can convert it to
517                         //       use a ThreadStatic allocation mitigator
518                         Adapter a = new Adapter (evaluator);
519
520                         return m.LTRReplace (this, input, new BaseMachine.MatchAppendEvaluator (a.Evaluate),
521                                                                  count, startat);
522                 }
523
524                 public string Replace (string input, string replacement)
525                 {
526                         return Replace (input, replacement, Int32.MaxValue, default_startat (input));
527                 }
528
529                 public string Replace (string input, string replacement, int count)
530                 {
531                         return Replace (input, replacement, count, default_startat (input));
532                 }
533
534                 public string Replace (string input, string replacement, int count, int startat)
535                 {
536                         if (input == null)
537                                 throw new ArgumentNullException ("input");
538                         if (replacement == null)
539                                 throw new ArgumentNullException ("replacement");
540                         if (count < -1)
541                                 throw new ArgumentOutOfRangeException ("count");
542                         if (startat < 0 || startat > input.Length)
543                                 throw new ArgumentOutOfRangeException ("startat");
544
545                         return CreateMachine ().Replace (this, input, replacement, count, startat);
546                 }
547
548                 // split methods
549
550                 public string [] Split (string input)
551                 {
552                         return Split (input, Int32.MaxValue, default_startat (input));
553                 }
554
555                 public string [] Split (string input, int count)
556                 {
557                         return Split (input, count, default_startat (input));
558                 }
559
560                 public string [] Split (string input, int count, int startat)
561                 {
562                         if (input == null)
563                                 throw new ArgumentNullException ("input");
564                         if (count < 0)
565                                 throw new ArgumentOutOfRangeException ("count");
566                         if (startat < 0 || startat > input.Length)
567                                 throw new ArgumentOutOfRangeException ("startat");
568
569                         return CreateMachine ().Split (this, input, count, startat);
570                 }
571
572                 // This method is called at the end of the constructor of compiled
573                 // regular expression classes to do internal initialization.
574                 protected void InitializeReferences ()
575                 {
576                         if (refsInitialized)
577                                 throw new NotSupportedException ("This operation is only allowed once per object.");
578
579                         refsInitialized = true;
580
581                         // Compile pattern that results in performance loss as existing
582                         // CIL code is ignored but provides support for regular
583                         // expressions compiled to assemblies.
584                         Init ();
585                 }
586 #if !NET_2_1
587                 protected bool UseOptionC ()
588                 {
589                         return ((roptions & RegexOptions.Compiled) != 0);
590                 }
591 #endif
592                 protected bool UseOptionR ()
593                 {
594                         return ((roptions & RegexOptions.RightToLeft) != 0);
595                 }
596
597                 // object methods
598                 
599                 public override string ToString ()
600                 {
601                         return pattern;
602                 }
603
604                 // ISerializable interface
605                 void ISerializable.GetObjectData (SerializationInfo info, StreamingContext context)
606                 {
607                         info.AddValue ("pattern", this.ToString (), typeof (string));
608                         info.AddValue ("options", this.Options, typeof (RegexOptions));
609                 }
610
611                 // internal
612
613                 internal int GroupCount {
614                         get { return group_count; }
615                 }
616
617                 internal int Gap {
618                         get { return gap; }
619                 }
620
621                 // private
622
623                 private IMachine CreateMachine ()
624                 {
625                         return machineFactory.NewInstance ();
626                 }
627
628                 private static string [] GetGroupNamesArray (int groupCount, IDictionary mapping) 
629                 {
630                         string [] group_names = new string [groupCount + 1];
631                         IDictionaryEnumerator de = mapping.GetEnumerator ();
632                         while (de.MoveNext ())
633                                 group_names [(int) de.Value] = (string) de.Key;
634                         return group_names;
635                 }
636
637                 private int [] GroupNumbers {
638                         get {
639                                 if (group_numbers == null) {
640                                         group_numbers = new int [1 + group_count];
641                                         for (int i = 0; i < gap; ++i)
642                                                 group_numbers [i] = i;
643                                         for (int i = gap; i <= group_count; ++i)
644                                                 group_numbers [i] = Int32.Parse (group_names [i]);
645                                         return group_numbers;
646                                 }
647                                 return group_numbers;
648                         }
649                 }
650
651                 private IMachineFactory machineFactory;
652                 private IDictionary mapping;
653                 private int group_count;
654                 private int gap;
655                 private bool refsInitialized;
656                 private string [] group_names;
657                 private int [] group_numbers;
658                 
659                 // protected members
660
661                 protected internal string pattern;
662                 protected internal RegexOptions roptions;
663                 
664                 // MS undocumented members
665 #if NET_2_1
666                 [MonoTODO]
667                 internal System.Collections.Generic.Dictionary<string, int> capnames;
668                 [MonoTODO]
669                 internal System.Collections.Generic.Dictionary<int, int> caps;
670 #else
671                 [MonoTODO]
672                 protected internal System.Collections.Hashtable capnames;
673                 [MonoTODO]
674                 protected internal System.Collections.Hashtable caps;
675
676                 [MonoTODO]
677                 protected internal RegexRunnerFactory factory;
678 #endif
679                 [MonoTODO]
680                 protected internal int capsize;
681                 [MonoTODO]
682                 protected internal string [] capslist;
683         }
684 }