2002-02-28 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                 internal static int _Compare (string strA, int indexA, string strB, int indexB,
321                                               int length, CultureInfo culture,
322                                               _StringCompareMode mode)
323
324                 {
325                         int i;
326
327                         /* When will the hurting stop!?!? */
328                         if (strA == null) {
329                                 if (strB == null)
330                                         return 0;
331                                 else
332                                         return -1;
333                         } else if (strB == null)
334                                 return 1;
335
336                         if (length < 0 || indexA < 0 || indexB < 0)
337                                 throw new ArgumentOutOfRangeException ();
338
339                         if (indexA > strA.Length || indexB > strB.Length)
340                                 throw new ArgumentOutOfRangeException ();
341
342                         // FIXME: Implement culture
343                         if (culture != null)
344                                 throw new NotImplementedException ();
345
346                         for (i = 0; i < length - 1; i++) {
347                                 if ((indexA+i >= strA.Length) || (indexB+i >= strB.Length))
348                                         break;
349
350                                 if (_CompareChar (strA[indexA+i], strB[indexB+i], culture, mode) != 0)
351                                         break;
352                 }
353
354                         if (indexA+i >= strA.Length) {
355                                 if (indexB+i >= strB.Length)
356                                         return 0;
357                                 else
358                                         return -1;
359                         } else if (indexB+i >= strB.Length)
360                                 return 1;
361
362                         return _CompareChar (strA[indexA+i], strB[indexB+i], culture, mode);
363                 }
364
365
366                 public static int Compare (string strA, string strB)
367                 {
368                         return Compare (strA, strB, false);
369                 }
370
371                 public static int Compare (string strA, string strB, bool ignoreCase)
372                 {
373                         return Compare (strA, strB, ignoreCase, null);
374                         }
375
376                 public static int Compare (string strA, string strB, bool ignoreCase, CultureInfo culture)
377                 {
378                         return Compare (strA, 0, strB, 0,
379                                         _CompareGetLength (strA, strB),
380                                         ignoreCase, culture);
381                 }
382
383                 public static int Compare (string strA, int indexA, string strB, int indexB, int length)
384                 {
385                         return  Compare (strA, indexA, strB, indexB, length, false);
386                 }
387
388                 public static int Compare (string strA, int indexA, string strB, int indexB,
389                                            int length, bool ignoreCase)
390                 {
391                         return Compare (strA, indexA, strB, indexB, length, ignoreCase, null);
392                 }
393
394                 public static int Compare (string strA, int indexA, string strB, int indexB,
395                                            int length, bool ignoreCase, CultureInfo culture)
396                 {
397                         _StringCompareMode mode;
398
399                         mode = ignoreCase ? _StringCompareMode.CompareCaseInsensitive :
400                                 _StringCompareMode.CompareDirect;
401
402                         return _Compare (strA, indexA, strB, indexB, length, culture, mode);
403                 }
404
405                 public static int CompareOrdinal (string strA, string strB)
406                 {
407                         return CompareOrdinal (strA, 0, strB, 0, _CompareGetLength (strA, strB));
408                         }
409
410                 public static int CompareOrdinal (string strA, int indexA, string strB, int indexB,
411                                                   int length)
412                 {
413                         return _Compare (strA, indexA, strB, indexB, length, null,
414                                          _StringCompareMode.CompareOrdinal);
415                 }
416
417                 public int CompareTo (object obj)
418                 {
419                         return Compare (this, obj == null ? null : obj.ToString ());
420                 }
421
422                 public int CompareTo (string str)
423                 {
424                         return Compare (this, str);
425                 }
426
427                 public static string Concat (object arg)
428                 {
429                         return arg != null ? arg.ToString () : String.Empty;
430                 }
431
432                 public static string Concat (params object[] args)
433                 {
434                         string[] strings;
435                         char[] str;
436                         int len, i;
437
438                         if (args == null)
439                                 throw new ArgumentNullException ();
440
441                         strings = new string [args.Length];
442                         len = 0;
443                         i = 0;
444                         foreach (object arg in args) {
445                                 /* use Empty for each null argument */
446                                 if (arg == null)
447                                         strings[i] = String.Empty;
448                                 else
449                                         strings[i] = arg.ToString ();
450                                 len += strings[i].length;
451                                 i++;
452                         }
453
454                         if (len == 0)
455                                 return String.Empty;
456
457                         String res = new String (len);
458                         str = res.c_str;
459                         i = 0;
460                         for (int j = 0; j < strings.Length; j++)
461                                 for (int k = 0; k < strings[j].length; k++)
462                                         str[i++] = strings[j].c_str[k];
463
464                         return res;
465                 }
466
467                 public static string Concat (params string[] values)
468                 {
469                         int len, i;
470                         char[] str;
471
472                         if (values == null)
473                                 throw new ArgumentNullException ();
474
475                         len = 0;
476                         foreach (string value in values)
477                                 len += value != null ? value.Length : 0;
478
479                         if (len == 0)
480                                 return String.Empty;
481
482                         String res = new String (len);
483                         str = res.c_str;
484                         i = 0;
485                         foreach (string value in values) {
486                                 if (value == null)
487                                         continue;
488
489                                 for (int j = 0; j < value.length; j++)
490                                         str[i++] = value.c_str[j];
491                         }
492
493                         return res;
494                 }
495
496                 public static string Concat (object arg0, object arg1)
497                 {
498                         string str0 = arg0 != null ? arg0.ToString () : String.Empty;
499                         string str1 = arg1 != null ? arg1.ToString () : String.Empty;
500
501                         return Concat (str0, str1);
502                 }
503
504                 public static string Concat (string str0, string str1)
505                 {
506                         char[] concat;
507                         int i, j, len;
508
509                         if (str0 == null)
510                                 str0 = String.Empty;
511                         if (str1 == null)
512                                 str1 = String.Empty;
513
514                         len = str0.length + str1.length;
515                         if (len == 0)
516                                 return String.Empty;
517
518                         String res = new String (len);
519
520                         concat = res.c_str;
521                         for (i = 0; i < str0.length; i++)
522                                 concat[i] = str0.c_str[i];
523                         for (j = 0 ; j < str1.length; j++)
524                                 concat[i + j] = str1.c_str[j];
525
526                         return res;
527                 }
528
529                 public static string Concat (object arg0, object arg1, object arg2)
530                 {
531                         string str0 = arg0 != null ? arg0.ToString () : String.Empty;
532                         string str1 = arg1 != null ? arg1.ToString () : String.Empty;
533                         string str2 = arg2 != null ? arg2.ToString () : String.Empty;
534
535                         return Concat (str0, str1, str2);
536                 }
537
538                 public static string Concat (string str0, string str1, string str2)
539                 {
540                         char[] concat;
541                         int i, j, k, len;
542
543                         if (str0 == null)
544                                 str0 = String.Empty;
545                         if (str1 == null)
546                                 str1 = String.Empty;
547                         if (str2 == null)
548                                 str2 = String.Empty;
549
550                         len = str0.length + str1.length + str2.length;
551                         if (len == 0)
552                                 return String.Empty;
553
554                         String res = new String (len);
555
556                         concat = res.c_str;
557                         for (i = 0; i < str0.length; i++)
558                                 concat[i] = str0.c_str[i];
559                         for (j = 0; j < str1.length; j++)
560                                 concat[i + j] = str1.c_str[j];
561                         for (k = 0; k < str2.length; k++)
562                                 concat[i + j + k] = str2.c_str[k];
563
564                         return res;
565                 }
566
567                 public static string Concat (string str0, string str1, string str2, string str3)
568                 {
569                         char[] concat;
570                         int i, j, k, l, len;
571
572                         if (str0 == null)
573                                 str0 = String.Empty;
574                         if (str1 == null)
575                                 str1 = String.Empty;
576                         if (str2 == null)
577                                 str2 = String.Empty;
578                         if (str3 == null)
579                                 str3 = String.Empty;
580
581                         len = str0.length + str1.length + str2.length + str3.length;
582                         if (len == 0)
583                                 return String.Empty;
584                         String res = new String (len);
585
586                         concat = res.c_str;
587                         for (i = 0; i < str0.length; i++)
588                                 concat[i] = str0.c_str[i];
589                         for (j = 0; j < str1.length; j++)
590                                 concat[i + j] = str1.c_str[j];
591                         for (k = 0; k < str2.length; k++)
592                                 concat[i + j + k] = str2.c_str[k];
593                         for (l = 0; l < str3.length; l++)
594                                 concat[i + j + k + l] = str3.c_str[l];
595
596                         return res;
597                 }
598
599                 public static string Copy (string str)
600                 {
601                         // FIXME: how do I *copy* a string if I can only have 1 of each?
602                         if (str == null)
603                                 throw new ArgumentNullException ();
604
605                         return str;
606                 }
607
608                 public void CopyTo (int sourceIndex, char[] destination, int destinationIndex, int count)
609                 {
610                         // LAMESPEC: should I null-terminate?
611                         int i;
612
613                         if (destination == null)
614                                 throw new ArgumentNullException ();
615
616                         if (sourceIndex < 0 || destinationIndex < 0 || count < 0)
617                                 throw new ArgumentOutOfRangeException ();
618
619                         if (sourceIndex + count > this.length)
620                                 throw new ArgumentOutOfRangeException ();
621
622                         if (destinationIndex + count > destination.Length)
623                                 throw new ArgumentOutOfRangeException ();
624
625                         for (i = 0; i < count; i++)
626                                 destination[destinationIndex + i] = this.c_str[sourceIndex + i];
627                 }
628
629                 public bool EndsWith (string value)
630                 {
631                         bool endswith = true;
632                         int start, i;
633
634                         if (value == null)
635                                 throw new ArgumentNullException ();
636
637                         start = this.length - value.length;
638                         if (start < 0)
639                                 return false;
640
641                         for (i = start; i < this.length && endswith; i++)
642                                 endswith = this.c_str[i] == value.c_str[i - start];
643
644                         return endswith;
645                 }
646
647                 public override bool Equals (object obj)
648                 {
649                         if (!(obj is String))
650                                 return false;
651
652                         return this == (String) obj;
653                 }
654
655                 public bool Equals (string value)
656                 {
657                         return this == value;
658                 }
659
660                 public static bool Equals (string a, string b)
661                 {
662                         return a == b;
663                 }
664
665                 public static string Format (string format, object arg0) {
666                         return Format (null, format, new object[] { arg0 });
667                 }
668
669                 public static string Format (string format, object arg0, object arg1) {
670                         return Format (null, format, new object[] { arg0, arg1 });
671                 }
672
673                 public static string Format (string format, object arg0, object arg1, object arg2) {
674                         return Format (null, format, new object[] { arg0, arg1, arg2 });
675                 }
676
677                 public static string Format (string format, params object[] args) {
678                         return Format (null, format, args);
679                 }
680
681                 public static string Format (IFormatProvider provider, string format, params object[] args) {
682                         if (format == null || args == null)
683                                 throw new ArgumentNullException ();
684                 
685                         StringBuilder result = new StringBuilder ();
686
687                         int ptr = 0;
688                         int start = ptr;
689                         while (ptr < format.Length) {
690                                 char c = format[ptr ++];
691
692                                 if (c == '{') {
693                                         result.Append (format, start, ptr - start - 1);
694
695                                         // check for escaped open bracket
696
697                                         if (format[ptr] == '{') {
698                                                 start = ptr ++;
699                                                 break;
700                                         }
701
702                                         // parse specifier
703                                 
704                                         int n, width;
705                                         bool left_align;
706                                         string arg_format;
707
708                                         ParseFormatSpecifier (format, ref ptr, out n, out width, out left_align, out arg_format);
709                                         if (n >= args.Length)
710                                                 throw new FormatException ("Index (zero based) must be greater than or equal to zero and less than the size of the argument list.");
711
712                                         // format argument
713
714                                         object arg = args[n];
715
716                                         string str;
717                                         if (arg == null)
718                                                 str = "";
719                                         else if (arg is IFormattable)
720                                                 str = ((IFormattable)arg).ToString (arg_format, provider);
721                                         else
722                                                 str = arg.ToString ();
723
724                                         // pad formatted string and append to result
725
726                                         if (width > str.Length) {
727                                                 string pad = new String (' ', width - str.Length);
728
729                                                 if (left_align) {
730                                                         result.Append (str);
731                                                         result.Append (pad);
732                                                 }
733                                                 else {
734                                                         result.Append (pad);
735                                                         result.Append (str);
736                                                 }
737                                         }
738                                         else
739                                                 result.Append (str);
740
741                                         start = ptr;
742                                 }
743                                 else if (c == '}' && format[ptr] == '}') {
744                                         result.Append (format, start, ptr - start - 1);
745                                         start = ptr ++;
746                                 }
747                         }
748
749                         if (start < format.Length)
750                                 result.Append (format.Substring (start));
751
752                         return result.ToString ();
753                 }
754
755                 public CharEnumerator GetEnumerator ()
756                 {
757                         return new CharEnumerator (this);
758                 }
759                 
760                 IEnumerator IEnumerable.GetEnumerator ()
761                 {
762                         return new CharEnumerator (this);
763                 }
764
765                 public override int GetHashCode ()
766                 {
767                         int h = 0;
768                         int i;
769                         for (i = 0; i < length; ++i)
770                                 h = (h << 5) - h + c_str [i];
771                         return h;
772                 }
773
774                 public TypeCode GetTypeCode ()
775                 {
776                         return TypeCode.String;
777                 }
778
779                 public int IndexOf (char value)
780                 {
781                         return IndexOf (value, 0, this.length);
782                 }
783
784                 public int IndexOf (string value)
785                 {
786                         return IndexOf (value, 0, this.length);
787                 }
788
789                 public int IndexOf (char value, int startIndex)
790                 {
791                         return IndexOf (value, startIndex, this.length - startIndex);
792                 }
793
794                 public int IndexOf (string value, int startIndex)
795                 {
796                         return IndexOf (value, startIndex, this.length - startIndex);
797                 }
798
799                 public int IndexOf (char value, int startIndex, int count)
800                 {
801                         int i;
802
803                         if (startIndex < 0 || count < 0 || startIndex + count > this.length)
804                                 throw new ArgumentOutOfRangeException ();
805
806                         for (i = startIndex; i - startIndex < count; i++)
807                                 if (this.c_str[i] == value)
808                                         return i;
809
810                         return -1;
811                 }
812
813                 public int IndexOf (string value, int startIndex, int count)
814                 {
815                         if (value == null)
816                                 throw new ArgumentNullException ();
817
818                         if (startIndex < 0 || count < 0 || startIndex + count > this.length)
819                                 throw new ArgumentOutOfRangeException ();
820
821                         return BoyerMoore (this.c_str, value, startIndex, count);
822 #if XXX
823                         int i;
824                         for (i = startIndex; i - startIndex + value.Length <= count; ) {
825                                 if (this.c_str[i] == value[0]) {
826                                         bool equal = true;
827                                         int j, nexti = 0;
828
829                                         for (j = 1; equal && j < value.Length; j++) {
830                                                 equal = this.c_str[i + j] == value[j];
831                                                 if (this.c_str[i + j] == value[0] && nexti == 0)
832                                                         nexti = i + j;
833                                         }
834
835                                         if (equal)
836                                                 return i;
837
838                                         if (nexti != 0)
839                                                 i = nexti;
840                                         else
841                                                 i += j;
842                                 } else
843                                         i++;
844                         }
845
846                         return -1;
847 #endif
848                 }
849
850                 public int IndexOfAny (char[] values)
851                 {
852                         return IndexOfAny (values, 0, this.length);
853                 }
854
855                 public int IndexOfAny (char[] values, int startIndex)
856                 {
857                         return IndexOfAny (values, startIndex, this.length - startIndex);
858                 }
859
860                 public int IndexOfAny (char[] values, int startIndex, int count)
861                 {
862                         if (values == null)
863                                 throw new ArgumentNullException ();
864
865                         if (startIndex < 0 || count < 0 || startIndex + count > this.length)
866                                 throw new ArgumentOutOfRangeException ();
867
868                         for (int i = startIndex; i < startIndex + count; i++) {
869                                 for (int j = 0; j < strlen (values); j++) {
870                                         if (this.c_str[i] == values[j])
871                                                 return i;
872                                 }
873                         }
874
875                         return -1;
876                 }
877
878                 public string Insert (int startIndex, string value)
879                 {
880                         char[] str;
881                         int i, j;
882
883                         if (value == null)
884                                 throw new ArgumentNullException ();
885
886                         if (startIndex < 0 || startIndex > this.length)
887                                 throw new ArgumentOutOfRangeException ();
888
889                         String res = new String (value.length + this.length);
890
891                         str = res.c_str;
892                         for (i = 0; i < startIndex; i++)
893                                 str[i] = this.c_str[i];
894                         for (j = 0; j < value.length; j++)
895                                 str[i + j] = value.c_str[j];
896                         for ( ; i < this.length; i++)
897                                 str[i + j] = this.c_str[i];
898
899                         return res;
900                 }
901
902                 [MethodImplAttribute(MethodImplOptions.InternalCall)]
903                 public extern static string Intern (string str);
904
905                 [MethodImplAttribute(MethodImplOptions.InternalCall)]
906                 public extern static string IsInterned (string str);
907
908                 public static string Join (string separator, string[] value)
909                 {
910                         return Join (separator, value, 0, value.Length);
911                 }
912
913                 public static string Join (string separator, string[] value, int startIndex, int count)
914                 {
915                         // LAMESPEC: msdn doesn't specify what happens when separator is null
916                         int len, i, j, used;
917                         char[] str;
918
919                         if (separator == null || value == null)
920                                 throw new ArgumentNullException ();
921
922                         if (startIndex + count > value.Length)
923                                 throw new ArgumentOutOfRangeException ();
924
925                         len = 0;
926                         for (i = startIndex, used = 0; used < count; i++, used++) {
927                                 if (i != startIndex)
928                                         len += separator.length;
929
930                                 len += value[i].length;
931                         }
932
933                         // We have no elements to join?
934                         if (i == startIndex)
935                                 return String.Empty;
936
937                         String res = new String (len);
938
939                         str = res.c_str;
940                         for (i = 0; i < value[startIndex].length; i++)
941                                 str[i] = value[startIndex][i];
942
943                         used = 1;
944                         for (j = startIndex + 1; used < count; j++, used++) {
945                                 int k;
946
947                                 for (k = 0; k < separator.length; k++)
948                                         str[i++] = separator.c_str[k];
949                                 for (k = 0; k < value[j].length; k++)
950                                         str[i++] = value[j].c_str[k];
951                         }
952
953                         return res;
954                 }
955
956                 public int LastIndexOf (char value)
957                 {
958                         int i = this.length;
959                         if (i == 0)
960                                 return -1;
961                         --i;
962                         for (; i >= 0; i--) {
963                                 if (this.c_str[i] == value)
964                                         return i;
965                         }
966
967                         return -1;
968                 }
969
970                 public int LastIndexOf (string value)
971                 {
972                         return LastIndexOf (value, this.length - 1, this.length);
973                 }
974
975                 public int LastIndexOf (char value, int startIndex)
976                 {
977                         if (startIndex < 0 || startIndex >= this.length)
978                                 throw new ArgumentOutOfRangeException ();
979
980                         for (int i = startIndex; i >= 0; i--) {
981                                 if (this.c_str[i] == value)
982                                         return i;
983                         }
984
985                         return -1;
986                 }
987
988                 public int LastIndexOf (string value, int startIndex)
989                 {
990                         return LastIndexOf (value, startIndex, startIndex + 1);
991                 }
992
993                 public int LastIndexOf (char value, int startIndex, int count)
994                 {
995                         if (startIndex < 0 || count < 0)
996                                 throw new ArgumentOutOfRangeException ();
997
998                         if (startIndex >= this.length || startIndex - count + 1 < 0)
999                                 throw new ArgumentOutOfRangeException ();
1000
1001                         for (int i = startIndex; i > startIndex - count; i--) {
1002                                 if (this.c_str[i] == value)
1003                                         return i;
1004                         }
1005
1006                         return -1;
1007                 }
1008
1009                 public int LastIndexOf (string value, int startIndex, int count)
1010                 {
1011                         // LAMESPEC: currently I'm using startIndex as the 0-position in the comparison,
1012                         //           but maybe it's the end-position in MS's implementation?
1013                         //           msdn is unclear on this point. I think this is correct though.
1014                         int i, len;
1015
1016                         if (value == null)
1017                                 throw new ArgumentNullException ();
1018
1019                         if (startIndex < 0 || startIndex >= this.length)
1020                                 throw new ArgumentOutOfRangeException ();
1021
1022                         if (count < 0 || startIndex - count + 1 < 0)
1023                                 throw new ArgumentOutOfRangeException ();
1024
1025                         if (value == String.Empty)
1026                                 return startIndex;
1027
1028                         if (startIndex + value.length > this.length) {
1029                                 /* just a little optimization */
1030                                 int start;
1031
1032                                 start = this.length - value.length;
1033                                 count -= startIndex - start;
1034                                 startIndex = start;
1035                         }
1036
1037                         // FIXME: use a reversed-unicode-safe-Boyer-Moore?
1038                         len = value.length - 1;
1039                         for (i = startIndex; i > startIndex - count; i--) {
1040                                 if (this.c_str[i + len] == value.c_str[len]) {
1041                                         bool equal = true;
1042                                         int j;
1043
1044                                         for (j = len - 1; equal && j >= 0; j--)
1045                                                 equal = this.c_str[i + j] == value.c_str[j];
1046
1047                                         if (equal)
1048                                                 return i;
1049                                 }
1050                         }
1051
1052                         return -1;
1053                 }
1054
1055                 public int LastIndexOfAny (char[] values)
1056                 {
1057                         return LastIndexOfAny (values, this.length - 1, this.length);
1058                 }
1059
1060                 public int LastIndexOfAny (char[] values, int startIndex)
1061                 {
1062                         return LastIndexOfAny (values, startIndex, startIndex + 1);
1063                 }
1064
1065                 public int LastIndexOfAny (char[] values, int startIndex, int count)
1066                 {
1067                         int i;
1068
1069                         if (values == null)
1070                                 throw new ArgumentNullException ();
1071
1072                         if (startIndex < 0 || count < 0 || startIndex - count + 1 < 0)
1073                                 throw new ArgumentOutOfRangeException ();
1074
1075                         for (i = startIndex; i > startIndex - count; i--) {
1076                                 for (int j = 0; j < strlen (values); j++) {
1077                                         if (this.c_str[i] == values[j])
1078                                                 return i;
1079                                 }
1080                         }
1081
1082                         return -1;
1083                 }
1084
1085                 public string PadLeft (int totalWidth)
1086                 {
1087                         return PadLeft (totalWidth, ' ');
1088                 }
1089
1090                 public string PadLeft (int totalWidth, char padChar)
1091                 {
1092                         char[] str;
1093                         int i, j;
1094
1095                         if (totalWidth < 0)
1096                                 throw new ArgumentException ();
1097
1098                         str = new char [totalWidth > this.length ? totalWidth : this.length];
1099                         for (i = 0; i < totalWidth - this.length; i++)
1100                                 str[i] = padChar;
1101
1102                         for (j = 0; j < this.length; i++, j++)
1103                                 str[i] = this.c_str[j];
1104
1105                         return new String (str);
1106                 }
1107
1108                 public string PadRight (int totalWidth)
1109                 {
1110                         return PadRight (totalWidth, ' ');
1111                 }
1112
1113                 public string PadRight (int totalWidth, char padChar)
1114                 {
1115                         char[] str;
1116                         int i;
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 < this.length; i++)
1123                                 str[i] = this.c_str[i];
1124
1125                         for ( ; i < str.Length; i++)
1126                                 str[i] = padChar;
1127
1128                         return new String (str);
1129                 }
1130
1131                 public string Remove (int startIndex, int count)
1132                 {
1133                         char[] str;
1134                         int i, j, len;
1135
1136                         if (startIndex < 0 || count < 0 || startIndex + count > this.length)
1137                                 throw new ArgumentOutOfRangeException ();
1138
1139                         len = this.length - count;
1140                         if (len == 0)
1141                                 return String.Empty;
1142                         
1143                         String res = new String (len);
1144                         str = res.c_str;
1145                         for (i = 0; i < startIndex; i++)
1146                                 str[i] = this.c_str[i];
1147                         for (j = i + count; j < this.length; j++)
1148                                 str[i++] = this.c_str[j];
1149
1150                         return res;
1151                 }
1152
1153                 public string Replace (char oldChar, char newChar)
1154                 {
1155                         char[] str;
1156                         int i;
1157
1158                         String res = new String (length);
1159                         str = res.c_str;
1160                         for (i = 0; i < this.length; i++) {
1161                                 if (this.c_str[i] == oldChar)
1162                                         str[i] = newChar;
1163                                 else
1164                                         str[i] = this.c_str[i];
1165                         }
1166
1167                         return res;
1168                 }
1169
1170                 public string Replace (string oldValue, string newValue)
1171                 {
1172                         // LAMESPEC: msdn doesn't specify what to do if either args is null
1173                         int index, len, i, j;
1174                         char[] str;
1175
1176                         if (oldValue == null || newValue == null)
1177                                 throw new ArgumentNullException ();
1178
1179                         // Use IndexOf in case I later rewrite it to use Boyer-Moore
1180                         index = IndexOf (oldValue, 0);
1181                         if (index == -1) {
1182                                 // This is the easy one ;-)
1183                                 return Substring (0, this.length);
1184                         }
1185
1186                         len = this.length - oldValue.length + newValue.length;
1187                         if (len == 0)
1188                                 return String.Empty;
1189
1190                         String res = new String (len);
1191                         str = res.c_str;
1192                         for (i = 0; i < index; i++)
1193                                 str[i] = this.c_str[i];
1194                         for (j = 0; j < newValue.length; j++)
1195                                 str[i++] = newValue[j];
1196                         for (j = index + oldValue.length; j < this.length; j++)
1197                                 str[i++] = this.c_str[j];
1198
1199                         return res;
1200                 }
1201
1202                 private int splitme (char[] separators, int startIndex)
1203                 {
1204                         /* this is basically a customized IndexOfAny() for the Split() methods */
1205                         for (int i = startIndex; i < this.length; i++) {
1206                                 if (separators != null) {
1207                                         foreach (char sep in separators) {
1208                                                 if (this.c_str[i] == sep)
1209                                                         return i - startIndex;
1210                                         }
1211                                 } else if (is_lwsp (this.c_str[i])) {
1212                                         return i - startIndex;
1213                                 }
1214                         }
1215
1216                         return -1;
1217                 }
1218
1219                 public string[] Split (params char[] separator)
1220                 {
1221                         /**
1222                          * split:
1223                          * @separator: delimiting chars or null to split on whtspc
1224                          *
1225                          * Returns: 1. An array consisting of a single
1226                          * element (@this) if none of the delimiting
1227                          * chars appear in @this. 2. An array of
1228                          * substrings which are delimited by one of
1229                          * the separator chars. 3. An array of
1230                          * substrings separated by whitespace if
1231                          * @separator is null. The Empty string should
1232                          * be returned wherever 2 delimiting chars are
1233                          * adjacent.
1234                          **/
1235                         // FIXME: would using a Queue be better?
1236                         string[] strings;
1237                         ArrayList list;
1238                         int index, len;
1239
1240                         list = new ArrayList ();
1241                         for (index = 0, len = 0; index < this.length; index += len + 1) {
1242                                 len = splitme (separator, index);
1243                                 len = len > -1 ? len : this.length - index;
1244                                 if (len == 0) {
1245                                         list.Add (String.Empty);
1246                                 } else {
1247                                         char[] str;
1248                                         int i;
1249
1250                                         str = new char [len];
1251                                         for (i = 0; i < len; i++)
1252                                                 str[i] = this.c_str[index + i];
1253
1254                                         list.Add (new String (str));
1255                                 }
1256                         }
1257
1258                         strings = new string [list.Count];
1259                         if (list.Count == 1) {
1260                                 /* special case for an array holding @this */
1261                                 strings[0] = this;
1262                         } else {
1263                                 for (index = 0; index < list.Count; index++)
1264                                         strings[index] = (string) list[index];
1265                         }
1266
1267                         return strings;
1268                 }
1269
1270                 public string[] Split (char[] separator, int maxCount)
1271                 {
1272                         // FIXME: what to do if maxCount <= 0?
1273                         // FIXME: would using Queue be better than ArrayList?
1274                         string[] strings;
1275                         ArrayList list;
1276                         int index, len, used;
1277
1278                         used = 0;
1279                         list = new ArrayList ();
1280                         for (index = 0, len = 0; index < this.length && used < maxCount; index += len + 1) {
1281                                 len = splitme (separator, index);
1282                                 len = len > -1 ? len : this.length - index;
1283                                 if (len == 0) {
1284                                         list.Add (String.Empty);
1285                                 } else {
1286                                         char[] str;
1287                                         int i;
1288
1289                                         str = new char [len];
1290                                         for (i = 0; i < len; i++)
1291                                                 str[i] = this.c_str[index + i];
1292
1293                                         list.Add (new String (str));
1294                                 }
1295                                 used++;
1296                         }
1297
1298                         /* fit the remaining chunk of the @this into it's own element */
1299                         if (index < this.length - 1) {
1300                                 char[] str;
1301                                 int i;
1302
1303                                 str = new char [this.length - index];
1304                                 for (i = index; i < this.length; i++)
1305                                         str[i - index] = this.c_str[i];
1306
1307                                 list.Add (new String (str));
1308                         }
1309
1310                         strings = new string [list.Count];
1311                         if (list.Count == 1) {
1312                                 /* special case for an array holding @this */
1313                                 strings[0] = this;
1314                         } else {
1315                                 for (index = 0; index < list.Count; index++)
1316                                         strings[index] = (string) list[index];
1317                         }
1318
1319                         return strings;
1320                 }
1321
1322                 public bool StartsWith (string value)
1323                 {
1324                         bool startswith = true;
1325                         int i;
1326
1327                         if (value == null)
1328                                 throw new ArgumentNullException ();
1329
1330                         if (value.length > this.length)
1331                                 return false;
1332
1333                         for (i = 0; i < value.length && startswith; i++)
1334                                 startswith = startswith && value.c_str[i] == this.c_str[i];
1335
1336                         return startswith;
1337                 }
1338
1339                 public string Substring (int startIndex)
1340                 {
1341                         char[] str;
1342                         int i, len;
1343
1344                         if (startIndex < 0 || startIndex > this.length)
1345                                 throw new ArgumentOutOfRangeException ();
1346
1347                         len = this.length - startIndex;
1348                         if (len == 0)
1349                                 return String.Empty;
1350                         String res = new String (len);
1351                         str = res.c_str;
1352                         for (i = startIndex; i < this.length; i++)
1353                                 str[i - startIndex] = this.c_str[i];
1354
1355                         return res;
1356                 }
1357
1358                 public string Substring (int startIndex, int length)
1359                 {
1360                         char[] str;
1361                         int i;
1362
1363                         if (startIndex < 0 || length < 0 || startIndex + length > this.length)
1364                                 throw new ArgumentOutOfRangeException ();
1365
1366                         if (length == 0)
1367                                 return String.Empty;
1368                         
1369                         String res = new String (length);
1370                         str = res.c_str;
1371                         for (i = startIndex; i < startIndex + length; i++)
1372                                 str[i - startIndex] = this.c_str[i];
1373
1374                         return res;
1375                 }
1376
1377                 bool IConvertible.ToBoolean (IFormatProvider provider)
1378                 {
1379                         return Convert.ToBoolean (this);
1380                 }
1381                 
1382                 byte IConvertible.ToByte (IFormatProvider provider)
1383                 {
1384                         return Convert.ToByte (this);
1385                 }
1386                 
1387                 char IConvertible.ToChar (IFormatProvider provider)
1388                 {
1389                         return Convert.ToChar (this);
1390                 }
1391
1392                 public char[] ToCharArray ()
1393                 {
1394                         return ToCharArray (0, this.length);
1395                 }
1396
1397                 public char[] ToCharArray (int startIndex, int length)
1398                 {
1399                         char[] chars;
1400                         int i;
1401
1402                         if (startIndex < 0 || length < 0 || startIndex + length > this.length)
1403                                 throw new ArgumentOutOfRangeException ();
1404
1405                         chars = new char [length];
1406                         for (i = startIndex; i < length; i++)
1407                                 chars[i - startIndex] = this.c_str[i];
1408
1409                         return chars;
1410                 }
1411
1412                 DateTime IConvertible.ToDateTime (IFormatProvider provider)
1413                 {
1414                         return Convert.ToDateTime (this);
1415                 }
1416
1417                 decimal IConvertible.ToDecimal (IFormatProvider provider)
1418                 {
1419                         return Convert.ToDecimal (this);
1420                 }
1421
1422                 double IConvertible.ToDouble (IFormatProvider provider)
1423                 {
1424                         return Convert.ToDouble (this);
1425                 }
1426
1427                 short IConvertible.ToInt16 (IFormatProvider provider)
1428                 {
1429                         return Convert.ToInt16 (this);
1430                 }
1431
1432                 int IConvertible.ToInt32 (IFormatProvider provider)
1433                 {
1434                         return Convert.ToInt32 (this);
1435                 }
1436
1437                 long IConvertible.ToInt64 (IFormatProvider provider)
1438                 {
1439                         return Convert.ToInt64 (this);
1440                 }
1441
1442                 public string ToLower ()
1443                 {
1444                         char[] str;
1445                         int i;
1446
1447                         String res = new String (length);
1448                         str = res.c_str;
1449                         for (i = 0; i < this.length; i++)
1450                                 str[i] = Char.ToLower (this.c_str[i]);
1451
1452                         return res;
1453                 }
1454
1455                 [MonoTODO]
1456                 public string ToLower (CultureInfo culture)
1457                 {
1458                         // FIXME: implement me
1459                         throw new NotImplementedException ();
1460
1461                 }
1462
1463                 [CLSCompliant(false)]
1464                 sbyte IConvertible.ToSByte (IFormatProvider provider)
1465                 {
1466                         return Convert.ToSByte (this);
1467                 }
1468
1469                 float IConvertible.ToSingle (IFormatProvider provider)
1470                 {
1471                         return Convert.ToSingle (this);
1472                 }
1473
1474                 public override string ToString ()
1475                 {
1476                         return this;
1477                 }
1478
1479                 string IConvertible.ToString (IFormatProvider format)
1480                 {
1481                         return this;
1482                 }
1483
1484                 object IConvertible.ToType (Type conversionType, IFormatProvider provider)
1485                 {
1486                         return Convert.ToType (this, conversionType,  provider);
1487                 }
1488
1489                 [CLSCompliant(false)]
1490                 ushort IConvertible.ToUInt16 (IFormatProvider provider)
1491                 {
1492                         return Convert.ToUInt16 (this);
1493                 }
1494
1495                 [CLSCompliant(false)]
1496                 uint IConvertible.ToUInt32 (IFormatProvider provider)
1497                 {
1498                         return Convert.ToUInt32 (this);
1499                 }
1500
1501                 [CLSCompliant(false)]
1502                 ulong IConvertible.ToUInt64 (IFormatProvider provider)
1503                 {
1504                         return Convert.ToUInt64 (this);
1505                 }
1506
1507                 public string ToUpper ()
1508                 {
1509                         char[] str;
1510                         int i;
1511
1512                         String res = new String (length);
1513                         str = res.c_str;
1514                         for (i = 0; i < this.length; i++)
1515                                 str[i] = Char.ToUpper (this.c_str[i]);
1516
1517                         return res;
1518                 }
1519
1520                 [MonoTODO]
1521                 public string ToUpper (CultureInfo culture)
1522                 {
1523                         // FIXME: implement me
1524                         throw new NotImplementedException ();
1525                 }
1526
1527                 public string Trim ()
1528                 {
1529                         return Trim (null);
1530                 }
1531
1532                 public string Trim (params char[] trimChars)
1533                 {
1534                         int begin, end;
1535                         bool matches = false;
1536
1537                         for (begin = 0; begin < this.length; begin++) {
1538                                 if (trimChars != null) {
1539                                         matches = false;
1540                                         foreach (char c in trimChars) {
1541                                                 matches = this.c_str[begin] == c;
1542                                                 if (matches)
1543                                                         break;
1544                                         }
1545                                         if (matches)
1546                                                 continue;
1547                                 } else {
1548                                         matches = is_lwsp (this.c_str[begin]);
1549                                         if (matches)
1550                                                 continue;
1551                                 }
1552                                 break;
1553                         }
1554
1555                         for (end = this.length - 1; end > begin; end--) {
1556                                 if (trimChars != null) {
1557                                         matches = false;
1558                                         foreach (char c in trimChars) {
1559                                                 matches = this.c_str[end] == c;
1560                                                 if (matches)
1561                                                         break;
1562                                         }
1563                                         if (matches)
1564                                                 continue;
1565                                 } else {
1566                                         matches = is_lwsp (this.c_str[end]);
1567                                         if (matches)
1568                                                 continue;
1569                                 }
1570                                 break;
1571                         }
1572                         end++;
1573
1574                         if (begin >= end)
1575                                 return String.Empty;
1576
1577                         return Substring (begin, end - begin);
1578                 }
1579
1580                 public string TrimEnd (params char[] trimChars)
1581                 {
1582                         bool matches = true;
1583                         int end;
1584
1585                         for (end = this.length - 1; matches && end > 0; end--) {
1586
1587                                 if (trimChars != null) {
1588                                         matches = false;
1589                                         foreach (char c in trimChars) {
1590                                                 matches = this.c_str[end] == c;
1591                                                 if (matches)
1592                                                         break;
1593                                         }
1594                                 } else {
1595                                         matches = is_lwsp (this.c_str[end]);
1596                                 }
1597
1598                                 if (!matches)
1599                                         return Substring (0, end+1);
1600                         }
1601
1602                         if (end == 0)
1603                                 return String.Empty;
1604
1605                         return Substring (0, end);
1606                 }
1607
1608                 public string TrimStart (params char[] trimChars)
1609                 {
1610                         bool matches = true;
1611                         int begin;
1612
1613                         for (begin = 0; matches && begin < this.length; begin++) {
1614                                 if (trimChars != null) {
1615                                         matches = false;
1616                                         foreach (char c in trimChars) {
1617                                                 matches = this.c_str[begin] == c;
1618                                                 if (matches)
1619                                                         break;
1620                                         }
1621                                 } else {
1622                                         matches = is_lwsp (this.c_str[begin]);
1623                                 }
1624
1625                                 if (!matches)
1626                                         return Substring (begin, this.length - begin);
1627                         }
1628
1629                                 return String.Empty;
1630                 }
1631
1632                 // Operators
1633                 public static bool operator ==(string a, string b)
1634                 {
1635                         if ((object)a == null) {
1636                                 if ((object)b == null)
1637                                         return true;
1638                                 return false;
1639                         }
1640                         if ((object)b == null)
1641                                 return false;
1642         
1643                         if (a.length != b.length)
1644                                 return false;
1645
1646                         int l = a.length;
1647                         for (int i = 0; i < l; i++)
1648                                 if (a.c_str[i] != b.c_str[i])
1649                                         return false;
1650
1651                         return true;
1652                 }
1653
1654                 public static bool operator !=(string a, string b)
1655                 {
1656                         return !(a == b);
1657                 }
1658
1659                 // private
1660
1661                 private static void ParseFormatSpecifier (string str, ref int ptr, out int n, out int width, out bool left_align, out string format) {
1662                         // parses format specifier of form:
1663                         //   N,[[-]M][:F]}
1664                         //
1665                         // where:
1666
1667                         try {
1668                                 // N = argument number (non-negative integer)
1669                         
1670                                 n = ParseDecimal (str, ref ptr);
1671                                 if (n < 0)
1672                                         throw new FormatException ("Input string was not in correct format.");
1673                                 
1674                                 // M = width (non-negative integer)
1675
1676                                 if (str[ptr] == ',') {
1677                                         left_align = (str[++ ptr] == '-');
1678                                         if (left_align)
1679                                                 ++ ptr;
1680
1681                                         width = ParseDecimal (str, ref ptr);
1682                                         if (width < 0)
1683                                                 throw new FormatException ("Input string was not in correct format.");
1684                                 }
1685                                 else {
1686                                         width = 0;
1687                                         left_align = false;
1688                                 }
1689
1690                                 // F = argument format (string)
1691
1692                                 if (str[ptr] == ':') {
1693                                         int start = ++ ptr;
1694                                         while (str[ptr] != '}')
1695                                                 ++ ptr;
1696
1697                                         format = str.Substring (start, ptr - start);
1698                                 }
1699                                 else
1700                                         format = null;
1701
1702                                 if (str[ptr ++] != '}')
1703                                         throw new FormatException ("Input string was not in correct format.");
1704                         }
1705                         catch (IndexOutOfRangeException) {
1706                                 throw new FormatException ("Input string was not in correct format.");
1707                         }
1708                 }
1709
1710                 private static int ParseDecimal (string str, ref int ptr) {
1711                         int p = ptr;
1712                         int n = 0;
1713                         while (true) {
1714                                 char c = str[p];
1715                                 if (c < '0' || '9' < c)
1716                                         break;
1717
1718                                 n = n * 10 + c - '0';
1719                                 ++ p;
1720                         }
1721
1722                         if (p == ptr)
1723                                 return -1;
1724                         
1725                         ptr = p;
1726                         return n;
1727                 }
1728         }
1729 }