2002-03-08 Martin Baulig <martin@gnome.org>
[mono.git] / mcs / class / corlib / System / String.cs
1 // -*- Mode: C++; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
2 //
3 // System.String.cs
4 //
5 // Authors:
6 //   Jeffrey Stedfast (fejj@ximian.com)
7 //   Dan Lewis (dihlewis@yahoo.co.uk)
8 //
9 // (C) 2001 Ximian, Inc.  http://www.ximian.com
10 //
11
12 // FIXME: from what I gather from msdn, when a function is to return an empty string
13 //        we should be returning this.Empty - some methods do this and others don't.
14
15 // FIXME: I didn't realise until later that `string' has a .Length method and so
16 //        I am missing some proper bounds-checking in some methods. Find these
17 //        instances and throw the ArgumentOutOfBoundsException at the programmer.
18 //        I like pelting programmers with ArgumentOutOfBoundsException's :-)
19
20 // FIXME: The ToLower(), ToUpper(), and Compare(..., bool ignoreCase) methods
21 //        need to be made unicode aware.
22
23 // FIXME: when you have a char carr[], does carr.Length include the terminating null char?
24
25 using System;
26 using System.Text;
27 using System.Collections;
28 using System.Globalization;
29 using System.Runtime.CompilerServices;
30
31 namespace System {
32
33         //[DefaultMemberName("Chars")]
34         public sealed class String : IComparable, ICloneable, IConvertible, IEnumerable {
35                 public static readonly string Empty = "";
36                 private char[] c_str;
37                 private int length;
38
39                 // Constructors
40
41                 internal String (int storage)
42                 {
43                         if (storage < 0)
44                                 throw new ArgumentOutOfRangeException ();
45                         length = storage;
46                         c_str = new char [storage];
47                 }
48                 
49                 [CLSCompliant(false)]
50                 unsafe public String (char *value)
51                 {
52                         int i;
53
54                         // FIXME: can I do value.Length here?
55                         if (value == null) {
56                                 this.length = 0;
57                         } else {
58                                 for (i = 0; *(value + i) != '\0'; i++);
59                                 this.length = i;
60                         }
61
62                         this.c_str = new char [this.length + 1];
63                         for (i = 0; i < this.length; i++)
64                                 this.c_str[i] = *(value + i);
65                 }
66
67                 public String (char[] value)
68                 {
69                         int i;
70
71                         // FIXME: value.Length includes the terminating null char?
72                         this.length = value != null ? strlen (value): 0;
73                         this.c_str = new char [this.length + 1];
74                         for (i = 0; i < this.length; i++)
75                                 this.c_str[i] = value[i];
76                 }
77
78                 [CLSCompliant(false)]
79                 unsafe public String (sbyte *value)
80                 {
81                         // FIXME: consider unicode?
82                         int i;
83
84                         // FIXME: can I do value.Length here? */
85                         if (value == null) {
86                                 this.length = 0;
87                         } else {
88                                 for (i = 0; *(value + i) != '\0'; i++);
89                                 this.length = i;
90                         }
91
92                         this.c_str = new char [this.length + 1];
93                         for (i = 0; i < this.length; i++)
94                                 this.c_str[i] = (char) *(value + i);
95                 }
96
97                 public String (char c, int count)
98                 {
99                         int i;
100
101                         this.length = count;
102                         this.c_str = new char [count + 1];
103                         for (i = 0; i < count; i++)
104                                 this.c_str[i] = c;
105                 }
106
107                 [CLSCompliant(false)]
108                 unsafe public String (char *value, int startIndex, int length)
109                 {
110                         int i;
111
112                         if (value == null && startIndex != 0 && length != 0)
113                                 throw new ArgumentNullException ();
114
115                         if (startIndex < 0 || length < 0)
116                                 throw new ArgumentOutOfRangeException ();
117
118                         this.length = length;
119                         this.c_str = new char [length + 1];
120                         for (i = 0; i < length; i++)
121                                 this.c_str[i] = *(value + startIndex + i);
122                 }
123
124                 public String (char[] value, int startIndex, int length)
125                 {
126                         int i;
127
128                         if (value == null && startIndex != 0 && length != 0)
129                                 throw new ArgumentNullException ();
130
131                         if (startIndex < 0 || length < 0)
132                                 throw new ArgumentOutOfRangeException ();
133
134                         this.length = length;
135                         this.c_str = new char [length + 1];
136                         for (i = 0; i < length; i++)
137                                 this.c_str[i] = value[startIndex + i];
138                 }
139
140                 [CLSCompliant(false)]
141                 unsafe public String (sbyte *value, int startIndex, int length)
142                 {
143                         // FIXME: consider unicode?
144                         int i;
145
146                         if (value == null && startIndex != 0 && length != 0)
147                                 throw new ArgumentNullException ();
148
149                         if (startIndex < 0 || length < 0)
150                                 throw new ArgumentOutOfRangeException ();
151
152                         this.length = length;
153                         this.c_str = new char [length + 1];
154                         for (i = 0; i < length; i++)
155                                 this.c_str[i] = (char) *(value + startIndex + i);
156                 }
157
158                 [CLSCompliant(false)][MonoTODO]
159                 unsafe public String (sbyte *value, int startIndex, int length, Encoding enc)
160                 {
161                         // FIXME: implement me
162                 }
163
164                 ~String ()
165                 {
166                         // FIXME: is there anything we need to do here?
167                         /*base.Finalize ();*/
168                 }
169
170                 // Properties
171                 public int Length {
172                         get {
173                                 return this.length;
174                         }
175                 }
176
177                 [IndexerName("Chars")]
178                 public char this [int index] {
179                         get {
180                                 if (index >= this.length)
181                                         throw new ArgumentOutOfRangeException ();
182
183                                 return this.c_str[index];
184                         }
185                 }
186
187                 // Private helper methods
188                 private static int strlen (char[] str)
189                 {
190                         // FIXME: if str.Length includes terminating null char, then return (str.Length - 1)
191                         return str.Length;
192                 }
193
194                 [MonoTODO]
195                 private static char tolowerordinal (char c)
196                 {
197                         // FIXME: implement me
198                         return c;
199                 }
200
201                 private static bool is_lwsp (char c)
202                 {
203                         /* this comes from the msdn docs for String.Trim() */
204                         if ((c >= '\x9' && c <= '\xD') || c == '\x20' || c == '\xA0' ||
205                             (c >= '\x2000' && c <= '\x200B') || c == '\x3000' || c == '\xFEFF')
206                                 return true;
207                         else
208                                 return false;
209                 }
210
211                 private static int BoyerMoore (char[] haystack, string needle, int startIndex, int count)
212                 {
213                         /* (hopefully) Unicode-safe Boyer-Moore implementation */
214                         int[] skiptable = new int[65536];  /* our unicode-safe skip-table */
215                         int h, n, he, ne, hc, nc, i;
216
217                         if (haystack == null || needle == null)
218                                 throw new ArgumentNullException ();
219
220                         /* if the search buffer is shorter than the pattern buffer, we can't match */
221                         if (count < needle.length)
222                                 return -1;
223
224                         /* return an instant match if the pattern is 0-length */
225                         if (needle.length == 0)
226                                 return startIndex;
227
228                         /* set a pointer at the end of each string */
229                         ne = needle.length - 1;      /* position of char before '\0' */
230                         he = startIndex + count;     /* position of last valid char */
231
232                         /* init the skip table with the pattern length */
233                         nc = needle.length;
234                         for (i = 0; i < 65536; i++)
235                                 skiptable[i] = nc;
236
237                         /* set the skip value for the chars that *do* appear in the
238                          * pattern buffer (needle) to the distance from the index to
239                          * the end of the pattern buffer. */
240                         for (nc = 0; nc < ne; nc++)
241                                 skiptable[(int) needle[nc]] = ne - nc;
242
243                         h = startIndex;
244                         while (count >= needle.length) {
245                                 hc = h + needle.length - 1;  /* set the haystack compare pointer */
246                                 nc = ne;                     /* set the needle compare pointer */
247
248                                 /* work our way backwards until they don't match */
249                                 for (i = 0; nc > 0; nc--, hc--, i++)
250                                         if (needle[nc] != haystack[hc])
251                                                 break;
252
253                                 if (needle[nc] != haystack[hc]) {
254                                         n = skiptable[(int) haystack[hc]] - i;
255                                         h += n;
256                                         count -= n;
257                                 } else
258                                         return h;
259                         }
260
261                         return -1;
262                 }
263
264                 // Methods
265                 [MonoTODO]
266                 public object Clone ()
267                 {
268                         // FIXME: implement me
269                         return null;
270                 }
271
272                 internal enum _StringCompareMode {
273                         CompareDirect,
274                         CompareCaseInsensitive,
275                         CompareOrdinal
276                 };
277
278                 internal static int _CompareGetLength (string strA, string strB)
279                 {
280                         if ((strA == null) || (strB == null))
281                                         return 0;
282                                 else
283                                 return Math.Max (strA.Length, strB.Length);
284                 }
285
286                 internal static int _CompareChar (char chrA, char chrB, CultureInfo culture,
287                                                   _StringCompareMode mode)
288                 {
289                         int result = 0;
290
291                         switch (mode) {
292                         case _StringCompareMode.CompareDirect:
293                                 // FIXME: We should do a culture based comparision here,
294                                 //        but for the moment let's do it by hand.
295                                 //        In the microsoft runtime, uppercase letters
296                                 //        sort after lowercase letters in the default
297                                 //        culture.
298                                 if (Char.IsUpper (chrA) && Char.IsLower (chrB))
299                                 return 1;
300                                 else if (Char.IsLower (chrA) && Char.IsUpper (chrB))
301                                 return -1;
302                                 result = (int) (chrA - chrB);
303                                 break;
304                         case _StringCompareMode.CompareCaseInsensitive:
305                                 result = (int) (Char.ToLower (chrA) - Char.ToLower (chrB));
306                                 break;
307                         case _StringCompareMode.CompareOrdinal:
308                                 result = (int) (tolowerordinal (chrA) - tolowerordinal (chrB));
309                                 break;
310                         }
311
312                         if (result == 0)
313                                         return 0;
314                         else if (result < 0)
315                                         return -1;
316                         else
317                                 return 1;
318                 }
319
320                 [MonoTODO]
321                 internal static int _Compare (string strA, int indexA, string strB, int indexB,
322                                               int length, CultureInfo culture,
323                                               _StringCompareMode mode)
324
325                 {
326                         int i;
327
328                         /* When will the hurting stop!?!? */
329                         if (strA == null) {
330                                 if (strB == null)
331                                         return 0;
332                                 else
333                                         return -1;
334                         } else if (strB == null)
335                                 return 1;
336
337                         if (length < 0 || indexA < 0 || indexB < 0)
338                                 throw new ArgumentOutOfRangeException ();
339
340                         if (indexA > strA.Length || indexB > strB.Length)
341                                 throw new ArgumentOutOfRangeException ();
342
343                         // FIXME: Implement culture
344                         if (culture != null)
345                                 throw new NotImplementedException ();
346
347                         for (i = 0; i < length - 1; i++) {
348                                 if ((indexA+i >= strA.Length) || (indexB+i >= strB.Length))
349                                         break;
350
351                                 if (_CompareChar (strA[indexA+i], strB[indexB+i], culture, mode) != 0)
352                                         break;
353                 }
354
355                         if (indexA+i >= strA.Length) {
356                                 if (indexB+i >= strB.Length)
357                                         return 0;
358                                 else
359                                         return -1;
360                         } else if (indexB+i >= strB.Length)
361                                 return 1;
362
363                         return _CompareChar (strA[indexA+i], strB[indexB+i], culture, mode);
364                 }
365
366
367                 public static int Compare (string strA, string strB)
368                 {
369                         return Compare (strA, strB, false);
370                 }
371
372                 public static int Compare (string strA, string strB, bool ignoreCase)
373                 {
374                         return Compare (strA, strB, ignoreCase, null);
375                         }
376
377                 public static int Compare (string strA, string strB, bool ignoreCase, CultureInfo culture)
378                 {
379                         return Compare (strA, 0, strB, 0,
380                                         _CompareGetLength (strA, strB),
381                                         ignoreCase, culture);
382                 }
383
384                 public static int Compare (string strA, int indexA, string strB, int indexB, int length)
385                 {
386                         return  Compare (strA, indexA, strB, indexB, length, false);
387                 }
388
389                 public static int Compare (string strA, int indexA, string strB, int indexB,
390                                            int length, bool ignoreCase)
391                 {
392                         return Compare (strA, indexA, strB, indexB, length, ignoreCase, null);
393                 }
394
395                 public static int Compare (string strA, int indexA, string strB, int indexB,
396                                            int length, bool ignoreCase, CultureInfo culture)
397                 {
398                         _StringCompareMode mode;
399
400                         mode = ignoreCase ? _StringCompareMode.CompareCaseInsensitive :
401                                 _StringCompareMode.CompareDirect;
402
403                         return _Compare (strA, indexA, strB, indexB, length, culture, mode);
404                 }
405
406                 public static int CompareOrdinal (string strA, string strB)
407                 {
408                         return CompareOrdinal (strA, 0, strB, 0, _CompareGetLength (strA, strB));
409                         }
410
411                 public static int CompareOrdinal (string strA, int indexA, string strB, int indexB,
412                                                   int length)
413                 {
414                         return _Compare (strA, indexA, strB, indexB, length, null,
415                                          _StringCompareMode.CompareOrdinal);
416                 }
417
418                 public int CompareTo (object obj)
419                 {
420                         return Compare (this, obj == null ? null : obj.ToString ());
421                 }
422
423                 public int CompareTo (string str)
424                 {
425                         return Compare (this, str);
426                 }
427
428                 public static string Concat (object arg)
429                 {
430                         return arg != null ? arg.ToString () : String.Empty;
431                 }
432
433                 public static string Concat (params object[] args)
434                 {
435                         string[] strings;
436                         char[] str;
437                         int len, i;
438
439                         if (args == null)
440                                 throw new ArgumentNullException ();
441
442                         strings = new string [args.Length];
443                         len = 0;
444                         i = 0;
445                         foreach (object arg in args) {
446                                 /* use Empty for each null argument */
447                                 if (arg == null)
448                                         strings[i] = String.Empty;
449                                 else
450                                         strings[i] = arg.ToString ();
451                                 len += strings[i].length;
452                                 i++;
453                         }
454
455                         if (len == 0)
456                                 return String.Empty;
457
458                         String res = new String (len);
459                         str = res.c_str;
460                         i = 0;
461                         for (int j = 0; j < strings.Length; j++)
462                                 for (int k = 0; k < strings[j].length; k++)
463                                         str[i++] = strings[j].c_str[k];
464
465                         return res;
466                 }
467
468                 public static string Concat (params string[] values)
469                 {
470                         int len, i;
471                         char[] str;
472
473                         if (values == null)
474                                 throw new ArgumentNullException ();
475
476                         len = 0;
477                         foreach (string value in values)
478                                 len += value != null ? value.Length : 0;
479
480                         if (len == 0)
481                                 return String.Empty;
482
483                         String res = new String (len);
484                         str = res.c_str;
485                         i = 0;
486                         foreach (string value in values) {
487                                 if (value == null)
488                                         continue;
489
490                                 for (int j = 0; j < value.length; j++)
491                                         str[i++] = value.c_str[j];
492                         }
493
494                         return res;
495                 }
496
497                 public static string Concat (object arg0, object arg1)
498                 {
499                         string str0 = arg0 != null ? arg0.ToString () : String.Empty;
500                         string str1 = arg1 != null ? arg1.ToString () : String.Empty;
501
502                         return Concat (str0, str1);
503                 }
504
505                 public static string Concat (string str0, string str1)
506                 {
507                         char[] concat;
508                         int i, j, len;
509
510                         if (str0 == null)
511                                 str0 = String.Empty;
512                         if (str1 == null)
513                                 str1 = String.Empty;
514
515                         len = str0.length + str1.length;
516                         if (len == 0)
517                                 return String.Empty;
518
519                         String res = new String (len);
520
521                         concat = res.c_str;
522                         for (i = 0; i < str0.length; i++)
523                                 concat[i] = str0.c_str[i];
524                         for (j = 0 ; j < str1.length; j++)
525                                 concat[i + j] = str1.c_str[j];
526
527                         return res;
528                 }
529
530                 public static string Concat (object arg0, object arg1, object arg2)
531                 {
532                         string str0 = arg0 != null ? arg0.ToString () : String.Empty;
533                         string str1 = arg1 != null ? arg1.ToString () : String.Empty;
534                         string str2 = arg2 != null ? arg2.ToString () : String.Empty;
535
536                         return Concat (str0, str1, str2);
537                 }
538
539                 public static string Concat (string str0, string str1, string str2)
540                 {
541                         char[] concat;
542                         int i, j, k, len;
543
544                         if (str0 == null)
545                                 str0 = String.Empty;
546                         if (str1 == null)
547                                 str1 = String.Empty;
548                         if (str2 == null)
549                                 str2 = String.Empty;
550
551                         len = str0.length + str1.length + str2.length;
552                         if (len == 0)
553                                 return String.Empty;
554
555                         String res = new String (len);
556
557                         concat = res.c_str;
558                         for (i = 0; i < str0.length; i++)
559                                 concat[i] = str0.c_str[i];
560                         for (j = 0; j < str1.length; j++)
561                                 concat[i + j] = str1.c_str[j];
562                         for (k = 0; k < str2.length; k++)
563                                 concat[i + j + k] = str2.c_str[k];
564
565                         return res;
566                 }
567
568                 public static string Concat (string str0, string str1, string str2, string str3)
569                 {
570                         char[] concat;
571                         int i, j, k, l, len;
572
573                         if (str0 == null)
574                                 str0 = String.Empty;
575                         if (str1 == null)
576                                 str1 = String.Empty;
577                         if (str2 == null)
578                                 str2 = String.Empty;
579                         if (str3 == null)
580                                 str3 = String.Empty;
581
582                         len = str0.length + str1.length + str2.length + str3.length;
583                         if (len == 0)
584                                 return String.Empty;
585                         String res = new String (len);
586
587                         concat = res.c_str;
588                         for (i = 0; i < str0.length; i++)
589                                 concat[i] = str0.c_str[i];
590                         for (j = 0; j < str1.length; j++)
591                                 concat[i + j] = str1.c_str[j];
592                         for (k = 0; k < str2.length; k++)
593                                 concat[i + j + k] = str2.c_str[k];
594                         for (l = 0; l < str3.length; l++)
595                                 concat[i + j + k + l] = str3.c_str[l];
596
597                         return res;
598                 }
599
600                 public static string Copy (string str)
601                 {
602                         // FIXME: how do I *copy* a string if I can only have 1 of each?
603                         if (str == null)
604                                 throw new ArgumentNullException ();
605
606                         return str;
607                 }
608
609                 public void CopyTo (int sourceIndex, char[] destination, int destinationIndex, int count)
610                 {
611                         // LAMESPEC: should I null-terminate?
612                         int i;
613
614                         if (destination == null)
615                                 throw new ArgumentNullException ();
616
617                         if (sourceIndex < 0 || destinationIndex < 0 || count < 0)
618                                 throw new ArgumentOutOfRangeException ();
619
620                         if (sourceIndex + count > this.length)
621                                 throw new ArgumentOutOfRangeException ();
622
623                         if (destinationIndex + count > destination.Length)
624                                 throw new ArgumentOutOfRangeException ();
625
626                         for (i = 0; i < count; i++)
627                                 destination[destinationIndex + i] = this.c_str[sourceIndex + i];
628                 }
629
630                 public bool EndsWith (string value)
631                 {
632                         bool endswith = true;
633                         int start, i;
634
635                         if (value == null)
636                                 throw new ArgumentNullException ();
637
638                         start = this.length - value.length;
639                         if (start < 0)
640                                 return false;
641
642                         for (i = start; i < this.length && endswith; i++)
643                                 endswith = this.c_str[i] == value.c_str[i - start];
644
645                         return endswith;
646                 }
647
648                 public override bool Equals (object obj)
649                 {
650                         if (!(obj is String))
651                                 return false;
652
653                         return this == (String) obj;
654                 }
655
656                 public bool Equals (string value)
657                 {
658                         return this == value;
659                 }
660
661                 public static bool Equals (string a, string b)
662                 {
663                         return a == b;
664                 }
665
666                 public static string Format (string format, object arg0) {
667                         return Format (null, format, new object[] { arg0 });
668                 }
669
670                 public static string Format (string format, object arg0, object arg1) {
671                         return Format (null, format, new object[] { arg0, arg1 });
672                 }
673
674                 public static string Format (string format, object arg0, object arg1, object arg2) {
675                         return Format (null, format, new object[] { arg0, arg1, arg2 });
676                 }
677
678                 public static string Format (string format, params object[] args) {
679                         return Format (null, format, args);
680                 }
681
682                 public static string Format (IFormatProvider provider, string format, params object[] args) {
683                         if (format == null || args == null)
684                                 throw new ArgumentNullException ();
685                 
686                         StringBuilder result = new StringBuilder ();
687
688                         int ptr = 0;
689                         int start = ptr;
690                         while (ptr < format.Length) {
691                                 char c = format[ptr ++];
692
693                                 if (c == '{') {
694                                         result.Append (format, start, ptr - start - 1);
695
696                                         // check for escaped open bracket
697
698                                         if (format[ptr] == '{') {
699                                                 start = ptr ++;
700                                                 continue;
701                                         }
702
703                                         // parse specifier
704                                 
705                                         int n, width;
706                                         bool left_align;
707                                         string arg_format;
708
709                                         ParseFormatSpecifier (format, ref ptr, out n, out width, out left_align, out arg_format);
710                                         if (n >= args.Length)
711                                                 throw new FormatException ("Index (zero based) must be greater than or equal to zero and less than the size of the argument list.");
712
713                                         // format argument
714
715                                         object arg = args[n];
716
717                                         string str;
718                                         if (arg == null)
719                                                 str = "";
720                                         else if (arg is IFormattable)
721                                                 str = ((IFormattable)arg).ToString (arg_format, provider);
722                                         else
723                                                 str = arg.ToString ();
724
725                                         // pad formatted string and append to result
726
727                                         if (width > str.Length) {
728                                                 string pad = new String (' ', width - str.Length);
729
730                                                 if (left_align) {
731                                                         result.Append (str);
732                                                         result.Append (pad);
733                                                 }
734                                                 else {
735                                                         result.Append (pad);
736                                                         result.Append (str);
737                                                 }
738                                         }
739                                         else
740                                                 result.Append (str);
741
742                                         start = ptr;
743                                 }
744                                 else if (c == '}' && format[ptr] == '}') {
745                                         result.Append (format, start, ptr - start - 1);
746                                         start = ptr ++;
747                                 }
748                         }
749
750                         if (start < format.Length)
751                                 result.Append (format.Substring (start));
752
753                         return result.ToString ();
754                 }
755
756                 public CharEnumerator GetEnumerator ()
757                 {
758                         return new CharEnumerator (this);
759                 }
760                 
761                 IEnumerator IEnumerable.GetEnumerator ()
762                 {
763                         return new CharEnumerator (this);
764                 }
765
766                 public override int GetHashCode ()
767                 {
768                         int h = 0;
769                         int i;
770                         for (i = 0; i < length; ++i)
771                                 h = (h << 5) - h + c_str [i];
772                         return h;
773                 }
774
775                 public TypeCode GetTypeCode ()
776                 {
777                         return TypeCode.String;
778                 }
779
780                 public int IndexOf (char value)
781                 {
782                         return IndexOf (value, 0, this.length);
783                 }
784
785                 public int IndexOf (string value)
786                 {
787                         return IndexOf (value, 0, this.length);
788                 }
789
790                 public int IndexOf (char value, int startIndex)
791                 {
792                         return IndexOf (value, startIndex, this.length - startIndex);
793                 }
794
795                 public int IndexOf (string value, int startIndex)
796                 {
797                         return IndexOf (value, startIndex, this.length - startIndex);
798                 }
799
800                 public int IndexOf (char value, int startIndex, int count)
801                 {
802                         int i;
803
804                         if (startIndex < 0 || count < 0 || startIndex + count > this.length)
805                                 throw new ArgumentOutOfRangeException ();
806
807                         for (i = startIndex; i - startIndex < count; i++)
808                                 if (this.c_str[i] == value)
809                                         return i;
810
811                         return -1;
812                 }
813
814                 public int IndexOf (string value, int startIndex, int count)
815                 {
816                         if (value == null)
817                                 throw new ArgumentNullException ();
818
819                         if (startIndex < 0 || count < 0 || startIndex + count > this.length)
820                                 throw new ArgumentOutOfRangeException ();
821
822 #if XXX
823                         return BoyerMoore (this.c_str, value, startIndex, count);
824 #endif
825                         int i;
826                         for (i = startIndex; i - startIndex + value.length <= count; ) {
827                                 if (this.c_str[i] == value.c_str [0]) {
828                                         bool equal = true;
829                                         int j, nexti = 0;
830
831                                         for (j = 1; equal && j < value.length; j++) {
832                                                 equal = this.c_str[i + j] == value.c_str [j];
833                                                 if (this.c_str [i + j] == value.c_str [0] && nexti == 0)
834                                                         nexti = i + j;
835                                         }
836
837                                         if (equal)
838                                                 return i;
839
840                                         if (nexti != 0)
841                                                 i = nexti;
842                                         else
843                                                 i += j;
844                                 } else
845                                         i++;
846                         }
847
848                         return -1;
849                 }
850
851                 public int IndexOfAny (char[] values)
852                 {
853                         return IndexOfAny (values, 0, this.length);
854                 }
855
856                 public int IndexOfAny (char[] values, int startIndex)
857                 {
858                         return IndexOfAny (values, startIndex, this.length - startIndex);
859                 }
860
861                 public int IndexOfAny (char[] values, int startIndex, int count)
862                 {
863                         if (values == null)
864                                 throw new ArgumentNullException ();
865
866                         if (startIndex < 0 || count < 0 || startIndex + count > this.length)
867                                 throw new ArgumentOutOfRangeException ();
868
869                         for (int i = startIndex; i < startIndex + count; i++) {
870                                 for (int j = 0; j < strlen (values); j++) {
871                                         if (this.c_str[i] == values[j])
872                                                 return i;
873                                 }
874                         }
875
876                         return -1;
877                 }
878
879                 public string Insert (int startIndex, string value)
880                 {
881                         char[] str;
882                         int i, j;
883
884                         if (value == null)
885                                 throw new ArgumentNullException ();
886
887                         if (startIndex < 0 || startIndex > this.length)
888                                 throw new ArgumentOutOfRangeException ();
889
890                         String res = new String (value.length + this.length);
891
892                         str = res.c_str;
893                         for (i = 0; i < startIndex; i++)
894                                 str[i] = this.c_str[i];
895                         for (j = 0; j < value.length; j++)
896                                 str[i + j] = value.c_str[j];
897                         for ( ; i < this.length; i++)
898                                 str[i + j] = this.c_str[i];
899
900                         return res;
901                 }
902
903                 [MethodImplAttribute(MethodImplOptions.InternalCall)]
904                 internal extern static string _Intern (string str);
905
906                 public static string Intern (string str)
907                 {
908                         if (str == null)
909                                 throw new ArgumentNullException ();
910
911                         return _Intern (str);
912                 }
913
914                 [MethodImplAttribute(MethodImplOptions.InternalCall)]
915                 internal extern static string _IsInterned (string str);
916
917                 public static string IsInterned (string str)
918                 {
919                         if (str == null)
920                                 throw new ArgumentNullException ();
921
922                         return _IsInterned (str);
923                 }
924
925                 public static string Join (string separator, string[] value)
926                 {
927                         if (value == null)
928                                 throw new ArgumentNullException ();
929
930                         return Join (separator, value, 0, value.Length);
931                 }
932
933                 public static string Join (string separator, string[] value, int startIndex, int count)
934                 {
935                         // LAMESPEC: msdn doesn't specify what happens when separator is null
936                         int len, i, j, used;
937                         char[] str;
938
939                         if (separator == null || value == null)
940                                 throw new ArgumentNullException ();
941
942                         if (startIndex + count > value.Length)
943                                 throw new ArgumentOutOfRangeException ();
944
945                         len = 0;
946                         for (i = startIndex, used = 0; used < count; i++, used++) {
947                                 if (i != startIndex)
948                                         len += separator.length;
949
950                                 len += value[i].length;
951                         }
952
953                         // We have no elements to join?
954                         if (i == startIndex)
955                                 return String.Empty;
956
957                         String res = new String (len);
958
959                         str = res.c_str;
960                         for (i = 0; i < value[startIndex].length; i++)
961                                 str[i] = value[startIndex][i];
962
963                         used = 1;
964                         for (j = startIndex + 1; used < count; j++, used++) {
965                                 int k;
966
967                                 for (k = 0; k < separator.length; k++)
968                                         str[i++] = separator.c_str[k];
969                                 for (k = 0; k < value[j].length; k++)
970                                         str[i++] = value[j].c_str[k];
971                         }
972
973                         return res;
974                 }
975
976                 public int LastIndexOf (char value)
977                 {
978                         int i = this.length;
979                         if (i == 0)
980                                 return -1;
981                         --i;
982                         for (; i >= 0; i--) {
983                                 if (this.c_str[i] == value)
984                                         return i;
985                         }
986
987                         return -1;
988                 }
989
990                 public int LastIndexOf (string value)
991                 {
992                         if (value == null)
993                                 throw new ArgumentNullException ();
994                         if (value.length == 0)
995                                 return 0;
996                         if (this.length == 0)
997                                 return -1;
998                                 
999                         return LastIndexOf (value, this.length - 1, this.length);
1000                 }
1001
1002                 public int LastIndexOf (char value, int startIndex)
1003                 {
1004                         if (startIndex < 0 || startIndex >= this.length)
1005                                 throw new ArgumentOutOfRangeException ();
1006
1007                         for (int i = startIndex; i >= 0; i--) {
1008                                 if (this.c_str[i] == value)
1009                                         return i;
1010                         }
1011
1012                         return -1;
1013                 }
1014
1015                 public int LastIndexOf (string value, int startIndex)
1016                 {
1017                         return LastIndexOf (value, startIndex, startIndex + 1);
1018                 }
1019
1020                 public int LastIndexOf (char value, int startIndex, int count)
1021                 {
1022                         if (startIndex < 0 || count < 0)
1023                                 throw new ArgumentOutOfRangeException ();
1024
1025                         if (startIndex >= this.length || startIndex - count + 1 < 0)
1026                                 throw new ArgumentOutOfRangeException ();
1027
1028                         for (int i = startIndex; i > startIndex - count; i--) {
1029                                 if (this.c_str[i] == value)
1030                                         return i;
1031                         }
1032
1033                         return -1;
1034                 }
1035
1036                 public int LastIndexOf (string value, int startIndex, int count)
1037                 {
1038                         // startIndex points to the end of value, ie. we're searching backwards.
1039                         int i, len;
1040
1041                         if (value == null)
1042                                 throw new ArgumentNullException ();
1043
1044                         if (startIndex < 0 || startIndex > this.length)
1045                                 throw new ArgumentOutOfRangeException ();
1046
1047                         if (count < 0 || startIndex - count + 1 < 0)
1048                                 throw new ArgumentOutOfRangeException ();
1049
1050                         if (value.length > startIndex)
1051                                 return -1;
1052
1053                         if (value == String.Empty)
1054                                 return startIndex;
1055
1056                         if (startIndex == this.length)
1057                                 startIndex--;
1058
1059                         // FIXME: use a reversed-unicode-safe-Boyer-Moore?
1060                         len = value.length - 1;
1061                         for (i = startIndex; i > startIndex - count; i--) {
1062
1063                                 if (this.c_str[i] == value.c_str[len]) {
1064                                         bool equal = true;
1065                                         int j;
1066
1067                                         for (j = 0; equal && j < len; j++)
1068                                                 equal = this.c_str[i - j] == value.c_str[len - j];
1069
1070                                         if (equal)
1071                                                 return i - j;
1072                                 }
1073                         }
1074
1075                         return -1;
1076                 }
1077
1078                 public int LastIndexOfAny (char[] values)
1079                 {
1080                         return LastIndexOfAny (values, this.length - 1, this.length);
1081                 }
1082
1083                 public int LastIndexOfAny (char[] values, int startIndex)
1084                 {
1085                         return LastIndexOfAny (values, startIndex, startIndex + 1);
1086                 }
1087
1088                 public int LastIndexOfAny (char[] values, int startIndex, int count)
1089                 {
1090                         int i;
1091
1092                         if (values == null)
1093                                 throw new ArgumentNullException ();
1094
1095                         if (startIndex < 0 || count < 0 || startIndex - count + 1 < 0)
1096                                 throw new ArgumentOutOfRangeException ();
1097
1098                         for (i = startIndex; i > startIndex - count; i--) {
1099                                 for (int j = 0; j < strlen (values); j++) {
1100                                         if (this.c_str[i] == values[j])
1101                                                 return i;
1102                                 }
1103                         }
1104
1105                         return -1;
1106                 }
1107
1108                 public string PadLeft (int totalWidth)
1109                 {
1110                         return PadLeft (totalWidth, ' ');
1111                 }
1112
1113                 public string PadLeft (int totalWidth, char padChar)
1114                 {
1115                         char[] str;
1116                         int i, j;
1117
1118                         if (totalWidth < 0)
1119                                 throw new ArgumentException ();
1120
1121                         str = new char [totalWidth > this.length ? totalWidth : this.length];
1122                         for (i = 0; i < totalWidth - this.length; i++)
1123                                 str[i] = padChar;
1124
1125                         for (j = 0; j < this.length; i++, j++)
1126                                 str[i] = this.c_str[j];
1127
1128                         return new String (str);
1129                 }
1130
1131                 public string PadRight (int totalWidth)
1132                 {
1133                         return PadRight (totalWidth, ' ');
1134                 }
1135
1136                 public string PadRight (int totalWidth, char padChar)
1137                 {
1138                         char[] str;
1139                         int i;
1140
1141                         if (totalWidth < 0)
1142                                 throw new ArgumentException ();
1143
1144                         str = new char [totalWidth > this.length ? totalWidth : this.length];
1145                         for (i = 0; i < this.length; i++)
1146                                 str[i] = this.c_str[i];
1147
1148                         for ( ; i < str.Length; i++)
1149                                 str[i] = padChar;
1150
1151                         return new String (str);
1152                 }
1153
1154                 public string Remove (int startIndex, int count)
1155                 {
1156                         char[] str;
1157                         int i, j, len;
1158
1159                         if (startIndex < 0 || count < 0 || startIndex + count > this.length)
1160                                 throw new ArgumentOutOfRangeException ();
1161
1162                         len = this.length - count;
1163                         if (len == 0)
1164                                 return String.Empty;
1165                         
1166                         String res = new String (len);
1167                         str = res.c_str;
1168                         for (i = 0; i < startIndex; i++)
1169                                 str[i] = this.c_str[i];
1170                         for (j = i + count; j < this.length; j++)
1171                                 str[i++] = this.c_str[j];
1172
1173                         return res;
1174                 }
1175
1176                 public string Replace (char oldChar, char newChar)
1177                 {
1178                         char[] str;
1179                         int i;
1180
1181                         String res = new String (length);
1182                         str = res.c_str;
1183                         for (i = 0; i < this.length; i++) {
1184                                 if (this.c_str[i] == oldChar)
1185                                         str[i] = newChar;
1186                                 else
1187                                         str[i] = this.c_str[i];
1188                         }
1189
1190                         return res;
1191                 }
1192
1193                 public string Replace (string oldValue, string newValue)
1194                 {
1195                         // If newValue is null, oldValue is removed.
1196                         int index, len, i, j, newlen;
1197                         string thestring;
1198                         char[] str;
1199
1200                         if (oldValue == null)
1201                                 throw new ArgumentNullException ();
1202
1203                         thestring = Substring (0, this.length);
1204                         index = 0;
1205
1206                         // Runs until all occurences of oldValue have been replaced.
1207                         while (true) {
1208                                 // Use IndexOf in case I later rewrite it to use Boyer-Moore
1209                                 index = thestring.IndexOf (oldValue, index);
1210
1211                                 if (index == -1)
1212                                         return thestring;
1213
1214                                 newlen = (newValue == null) ? 0 : newValue.length;
1215                                 len = thestring.length - oldValue.length + newlen;
1216
1217                                 if (len == 0)
1218                                         return String.Empty;
1219
1220                                 String res = new String (len);
1221                                 str = res.c_str;
1222                                 for (i = 0; i < index; i++)
1223                                         str[i] = thestring.c_str[i];
1224                                 for (j = 0; j < newlen; j++)
1225                                         str[i++] = newValue[j];
1226                                 for (j = index + oldValue.length; j < thestring.length; j++)
1227                                         str[i++] = thestring.c_str[j];
1228
1229                                 // Increment index, we're already done replacing until this index.
1230                                 thestring = res;
1231                                 index += newlen;
1232                         }
1233                 }
1234
1235                 private int splitme (char[] separators, int startIndex)
1236                 {
1237                         /* this is basically a customized IndexOfAny() for the Split() methods */
1238                         for (int i = startIndex; i < this.length; i++) {
1239                                 if (separators != null) {
1240                                         foreach (char sep in separators) {
1241                                                 if (this.c_str[i] == sep)
1242                                                         return i - startIndex;
1243                                         }
1244                                 } else if (is_lwsp (this.c_str[i])) {
1245                                         return i - startIndex;
1246                                 }
1247                         }
1248
1249                         return -1;
1250                 }
1251
1252                 public string[] Split (params char[] separator)
1253                 {
1254                         /**
1255                          * split:
1256                          * @separator: delimiting chars or null to split on whtspc
1257                          *
1258                          * Returns: 1. An array consisting of a single
1259                          * element (@this) if none of the delimiting
1260                          * chars appear in @this. 2. An array of
1261                          * substrings which are delimited by one of
1262                          * the separator chars. 3. An array of
1263                          * substrings separated by whitespace if
1264                          * @separator is null. The Empty string should
1265                          * be returned wherever 2 delimiting chars are
1266                          * adjacent.
1267                          **/
1268                         // FIXME: would using a Queue be better?
1269                         string[] strings;
1270                         ArrayList list;
1271                         int index, len;
1272
1273                         list = new ArrayList ();
1274                         for (index = 0, len = 0; index < this.length; index += len + 1) {
1275                                 len = splitme (separator, index);
1276                                 len = len > -1 ? len : this.length - index;
1277                                 if (len == 0) {
1278                                         list.Add (String.Empty);
1279                                 } else {
1280                                         char[] str;
1281                                         int i;
1282
1283                                         str = new char [len];
1284                                         for (i = 0; i < len; i++)
1285                                                 str[i] = this.c_str[index + i];
1286
1287                                         list.Add (new String (str));
1288                                 }
1289                         }
1290
1291                         strings = new string [list.Count];
1292                         if (list.Count == 1) {
1293                                 /* special case for an array holding @this */
1294                                 strings[0] = this;
1295                         } else {
1296                                 for (index = 0; index < list.Count; index++)
1297                                         strings[index] = (string) list[index];
1298                         }
1299
1300                         return strings;
1301                 }
1302
1303                 public string[] Split (char[] separator, int maxCount)
1304                 {
1305                         // FIXME: would using Queue be better than ArrayList?
1306                         string[] strings;
1307                         ArrayList list;
1308                         int index, len, used;
1309
1310                         if (maxCount == 0)
1311                                 return new string[0];
1312                         else if (maxCount < 0)
1313                                 throw new ArgumentOutOfRangeException ();
1314
1315                         used = 0;
1316                         list = new ArrayList ();
1317                         for (index = 0, len = 0; index < this.length && used < maxCount; index += len + 1) {
1318                                 len = splitme (separator, index);
1319                                 len = len > -1 ? len : this.length - index;
1320                                 if (len == 0) {
1321                                         list.Add (String.Empty);
1322                                 } else {
1323                                         char[] str;
1324                                         int i;
1325
1326                                         str = new char [len];
1327                                         for (i = 0; i < len; i++)
1328                                                 str[i] = this.c_str[index + i];
1329
1330                                         list.Add (new String (str));
1331                                 }
1332                                 used++;
1333                         }
1334
1335                         /* fit the remaining chunk of the @this into it's own element */
1336                         if (index <= this.length)
1337                         {
1338                                 char[] str;
1339                                 int i;
1340
1341                                 str = new char [this.length - index];
1342                                 for (i = index; i < this.length; i++)
1343                                         str[i - index] = this.c_str[i];
1344
1345                                 // maxCount cannot be zero if we reach this point and this means that
1346                                 // index can't be zero either.
1347                                 if (used == maxCount)
1348                                         list[used-1] += this.c_str[index-1] + new String (str);
1349                                 else
1350                                 list.Add (new String (str));
1351                         }
1352
1353                         strings = new string [list.Count];
1354                         if (list.Count == 1) {
1355                                 /* special case for an array holding @this */
1356                                 strings[0] = this;
1357                         } else {
1358                                 for (index = 0; index < list.Count; index++)
1359                                         strings[index] = (string) list[index];
1360                         }
1361
1362                         return strings;
1363                 }
1364
1365                 public bool StartsWith (string value)
1366                 {
1367                         bool startswith = true;
1368                         int i;
1369
1370                         if (value == null)
1371                                 throw new ArgumentNullException ();
1372
1373                         if (value.length > this.length)
1374                                 return false;
1375
1376                         for (i = 0; i < value.length && startswith; i++)
1377                                 startswith = startswith && value.c_str[i] == this.c_str[i];
1378
1379                         return startswith;
1380                 }
1381
1382                 public string Substring (int startIndex)
1383                 {
1384                         char[] str;
1385                         int i, len;
1386
1387                         if (startIndex < 0 || startIndex > this.length)
1388                                 throw new ArgumentOutOfRangeException ();
1389
1390                         len = this.length - startIndex;
1391                         if (len == 0)
1392                                 return String.Empty;
1393                         String res = new String (len);
1394                         str = res.c_str;
1395                         for (i = startIndex; i < this.length; i++)
1396                                 str[i - startIndex] = this.c_str[i];
1397
1398                         return res;
1399                 }
1400
1401                 public string Substring (int startIndex, int length)
1402                 {
1403                         char[] str;
1404                         int i;
1405
1406                         if (startIndex < 0 || length < 0 || startIndex + length > this.length)
1407                                 throw new ArgumentOutOfRangeException ();
1408
1409                         if (length == 0)
1410                                 return String.Empty;
1411                         
1412                         String res = new String (length);
1413                         str = res.c_str;
1414                         for (i = startIndex; i < startIndex + length; i++)
1415                                 str[i - startIndex] = this.c_str[i];
1416
1417                         return res;
1418                 }
1419
1420                 bool IConvertible.ToBoolean (IFormatProvider provider)
1421                 {
1422                         return Convert.ToBoolean (this);
1423                 }
1424                 
1425                 byte IConvertible.ToByte (IFormatProvider provider)
1426                 {
1427                         return Convert.ToByte (this);
1428                 }
1429                 
1430                 char IConvertible.ToChar (IFormatProvider provider)
1431                 {
1432                         return Convert.ToChar (this);
1433                 }
1434
1435                 public char[] ToCharArray ()
1436                 {
1437                         return ToCharArray (0, this.length);
1438                 }
1439
1440                 public char[] ToCharArray (int startIndex, int length)
1441                 {
1442                         char[] chars;
1443                         int i;
1444
1445                         if (startIndex < 0 || length < 0 || startIndex + length > this.length)
1446                                 throw new ArgumentOutOfRangeException ();
1447
1448                         chars = new char [length];
1449                         for (i = startIndex; i < length; i++)
1450                                 chars[i - startIndex] = this.c_str[i];
1451
1452                         return chars;
1453                 }
1454
1455                 DateTime IConvertible.ToDateTime (IFormatProvider provider)
1456                 {
1457                         return Convert.ToDateTime (this);
1458                 }
1459
1460                 decimal IConvertible.ToDecimal (IFormatProvider provider)
1461                 {
1462                         return Convert.ToDecimal (this);
1463                 }
1464
1465                 double IConvertible.ToDouble (IFormatProvider provider)
1466                 {
1467                         return Convert.ToDouble (this);
1468                 }
1469
1470                 short IConvertible.ToInt16 (IFormatProvider provider)
1471                 {
1472                         return Convert.ToInt16 (this);
1473                 }
1474
1475                 int IConvertible.ToInt32 (IFormatProvider provider)
1476                 {
1477                         return Convert.ToInt32 (this);
1478                 }
1479
1480                 long IConvertible.ToInt64 (IFormatProvider provider)
1481                 {
1482                         return Convert.ToInt64 (this);
1483                 }
1484
1485                 public string ToLower ()
1486                 {
1487                         char[] str;
1488                         int i;
1489
1490                         String res = new String (length);
1491                         str = res.c_str;
1492                         for (i = 0; i < this.length; i++)
1493                                 str[i] = Char.ToLower (this.c_str[i]);
1494
1495                         return res;
1496                 }
1497
1498                 [MonoTODO]
1499                 public string ToLower (CultureInfo culture)
1500                 {
1501                         // FIXME: implement me
1502                         throw new NotImplementedException ();
1503
1504                 }
1505
1506                 [CLSCompliant(false)]
1507                 sbyte IConvertible.ToSByte (IFormatProvider provider)
1508                 {
1509                         return Convert.ToSByte (this);
1510                 }
1511
1512                 float IConvertible.ToSingle (IFormatProvider provider)
1513                 {
1514                         return Convert.ToSingle (this);
1515                 }
1516
1517                 public override string ToString ()
1518                 {
1519                         return this;
1520                 }
1521
1522                 string IConvertible.ToString (IFormatProvider format)
1523                 {
1524                         return this;
1525                 }
1526
1527                 object IConvertible.ToType (Type conversionType, IFormatProvider provider)
1528                 {
1529                         return Convert.ToType (this, conversionType,  provider);
1530                 }
1531
1532                 [CLSCompliant(false)]
1533                 ushort IConvertible.ToUInt16 (IFormatProvider provider)
1534                 {
1535                         return Convert.ToUInt16 (this);
1536                 }
1537
1538                 [CLSCompliant(false)]
1539                 uint IConvertible.ToUInt32 (IFormatProvider provider)
1540                 {
1541                         return Convert.ToUInt32 (this);
1542                 }
1543
1544                 [CLSCompliant(false)]
1545                 ulong IConvertible.ToUInt64 (IFormatProvider provider)
1546                 {
1547                         return Convert.ToUInt64 (this);
1548                 }
1549
1550                 public string ToUpper ()
1551                 {
1552                         char[] str;
1553                         int i;
1554
1555                         String res = new String (length);
1556                         str = res.c_str;
1557                         for (i = 0; i < this.length; i++)
1558                                 str[i] = Char.ToUpper (this.c_str[i]);
1559
1560                         return res;
1561                 }
1562
1563                 [MonoTODO]
1564                 public string ToUpper (CultureInfo culture)
1565                 {
1566                         // FIXME: implement me
1567                         throw new NotImplementedException ();
1568                 }
1569
1570                 public string Trim ()
1571                 {
1572                         return Trim (null);
1573                 }
1574
1575                 public string Trim (params char[] trimChars)
1576                 {
1577                         int begin, end;
1578                         bool matches = false;
1579
1580                         for (begin = 0; begin < this.length; begin++) {
1581                                 if (trimChars != null) {
1582                                         matches = false;
1583                                         foreach (char c in trimChars) {
1584                                                 matches = this.c_str[begin] == c;
1585                                                 if (matches)
1586                                                         break;
1587                                         }
1588                                         if (matches)
1589                                                 continue;
1590                                 } else {
1591                                         matches = is_lwsp (this.c_str[begin]);
1592                                         if (matches)
1593                                                 continue;
1594                                 }
1595                                 break;
1596                         }
1597
1598                         for (end = this.length - 1; end > begin; end--) {
1599                                 if (trimChars != null) {
1600                                         matches = false;
1601                                         foreach (char c in trimChars) {
1602                                                 matches = this.c_str[end] == c;
1603                                                 if (matches)
1604                                                         break;
1605                                         }
1606                                         if (matches)
1607                                                 continue;
1608                                 } else {
1609                                         matches = is_lwsp (this.c_str[end]);
1610                                         if (matches)
1611                                                 continue;
1612                                 }
1613                                 break;
1614                         }
1615                         end++;
1616
1617                         if (begin >= end)
1618                                 return String.Empty;
1619
1620                         return Substring (begin, end - begin);
1621                 }
1622
1623                 public string TrimEnd (params char[] trimChars)
1624                 {
1625                         bool matches = true;
1626                         int end;
1627
1628                         for (end = this.length - 1; matches && end > 0; end--) {
1629
1630                                 if (trimChars != null) {
1631                                         matches = false;
1632                                         foreach (char c in trimChars) {
1633                                                 matches = this.c_str[end] == c;
1634                                                 if (matches)
1635                                                         break;
1636                                         }
1637                                 } else {
1638                                         matches = is_lwsp (this.c_str[end]);
1639                                 }
1640
1641                                 if (!matches)
1642                                         return Substring (0, end+1);
1643                         }
1644
1645                         if (end == 0)
1646                                 return String.Empty;
1647
1648                         return Substring (0, end);
1649                 }
1650
1651                 public string TrimStart (params char[] trimChars)
1652                 {
1653                         bool matches = true;
1654                         int begin;
1655
1656                         for (begin = 0; matches && begin < this.length; begin++) {
1657                                 if (trimChars != null) {
1658                                         matches = false;
1659                                         foreach (char c in trimChars) {
1660                                                 matches = this.c_str[begin] == c;
1661                                                 if (matches)
1662                                                         break;
1663                                         }
1664                                 } else {
1665                                         matches = is_lwsp (this.c_str[begin]);
1666                                 }
1667
1668                                 if (!matches)
1669                                         return Substring (begin, this.length - begin);
1670                         }
1671
1672                                 return String.Empty;
1673                 }
1674
1675                 // Operators
1676                 public static bool operator ==(string a, string b)
1677                 {
1678                         if ((object)a == null) {
1679                                 if ((object)b == null)
1680                                         return true;
1681                                 return false;
1682                         }
1683                         if ((object)b == null)
1684                                 return false;
1685         
1686                         if (a.length != b.length)
1687                                 return false;
1688
1689                         int l = a.length;
1690                         for (int i = 0; i < l; i++)
1691                                 if (a.c_str[i] != b.c_str[i])
1692                                         return false;
1693
1694                         return true;
1695                 }
1696
1697                 public static bool operator !=(string a, string b)
1698                 {
1699                         return !(a == b);
1700                 }
1701
1702                 // private
1703
1704                 private static void ParseFormatSpecifier (string str, ref int ptr, out int n, out int width, out bool left_align, out string format) {
1705                         // parses format specifier of form:
1706                         //   N,[[-]M][:F]}
1707                         //
1708                         // where:
1709
1710                         try {
1711                                 // N = argument number (non-negative integer)
1712                         
1713                                 n = ParseDecimal (str, ref ptr);
1714                                 if (n < 0)
1715                                         throw new FormatException ("Input string was not in correct format.");
1716                                 
1717                                 // M = width (non-negative integer)
1718
1719                                 if (str[ptr] == ',') {
1720                                         left_align = (str[++ ptr] == '-');
1721                                         if (left_align)
1722                                                 ++ ptr;
1723
1724                                         width = ParseDecimal (str, ref ptr);
1725                                         if (width < 0)
1726                                                 throw new FormatException ("Input string was not in correct format.");
1727                                 }
1728                                 else {
1729                                         width = 0;
1730                                         left_align = false;
1731                                 }
1732
1733                                 // F = argument format (string)
1734
1735                                 if (str[ptr] == ':') {
1736                                         int start = ++ ptr;
1737                                         while (str[ptr] != '}')
1738                                                 ++ ptr;
1739
1740                                         format = str.Substring (start, ptr - start);
1741                                 }
1742                                 else
1743                                         format = null;
1744
1745                                 if (str[ptr ++] != '}')
1746                                         throw new FormatException ("Input string was not in correct format.");
1747                         }
1748                         catch (IndexOutOfRangeException) {
1749                                 throw new FormatException ("Input string was not in correct format.");
1750                         }
1751                 }
1752
1753                 private static int ParseDecimal (string str, ref int ptr) {
1754                         int p = ptr;
1755                         int n = 0;
1756                         while (true) {
1757                                 char c = str[p];
1758                                 if (c < '0' || '9' < c)
1759                                         break;
1760
1761                                 n = n * 10 + c - '0';
1762                                 ++ p;
1763                         }
1764
1765                         if (p == ptr)
1766                                 return -1;
1767                         
1768                         ptr = p;
1769                         return n;
1770                 }
1771         }
1772 }