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