wasCSharpSQLite – Blame information for rev 1

Subversion Repositories:
Rev:
Rev Author Line No. Line
1 office 1 /*
2 * StringCmd.java
3 *
4 * Copyright (c) 1997 Cornell University.
5 * Copyright (c) 1997 Sun Microsystems, Inc.
6 * Copyright (c) 1998-2000 Scriptics Corporation.
7 * Copyright (c) 2000 Christian Krone.
8 *
9 * See the file "license.terms" for information on usage and
10 * redistribution of this file, and for a DISCLAIMER OF ALL
11 * WARRANTIES.
12 *
13 * Included in SQLite3 port to C# for use in testharness only; 2008 Noah B Hart
14 *
15 * RCS @(#) $Id: StringCmd.java,v 1.4 2000/08/20 08:37:47 mo Exp $
16 *
17 */
18 using System.Text;
19 namespace tcl.lang
20 {
21  
22 /// <summary> This class implements the built-in "string" command in Tcl.</summary>
23  
24 class StringCmd : Command
25 {
26  
27 private static readonly string[] options = new string[] { "bytelength", "compare", "equal", "first", "index", "is", "last", "length", "map", "match", "range", "repeat", "replace", "tolower", "toupper", "totitle", "trim", "trimleft", "trimright", "wordend", "wordstart" };
28 private const int STR_BYTELENGTH = 0;
29 private const int STR_COMPARE = 1;
30 private const int STR_EQUAL = 2;
31 private const int STR_FIRST = 3;
32 private const int STR_INDEX = 4;
33 private const int STR_IS = 5;
34 private const int STR_LAST = 6;
35 private const int STR_LENGTH = 7;
36 private const int STR_MAP = 8;
37 private const int STR_MATCH = 9;
38 private const int STR_RANGE = 10;
39 private const int STR_REPEAT = 11;
40 private const int STR_REPLACE = 12;
41 private const int STR_TOLOWER = 13;
42 private const int STR_TOUPPER = 14;
43 private const int STR_TOTITLE = 15;
44 private const int STR_TRIM = 16;
45 private const int STR_TRIMLEFT = 17;
46 private const int STR_TRIMRIGHT = 18;
47 private const int STR_WORDEND = 19;
48 private const int STR_WORDSTART = 20;
49  
50 private static readonly string[] isOptions = new string[] { "alnum", "alpha", "ascii", "control", "boolean", "digit", "double", "false", "graph", "integer", "lower", "print", "punct", "space", "true", "upper", "wideinteger", "wordchar", "xdigit" };
51 private const int STR_IS_ALNUM = 0;
52 private const int STR_IS_ALPHA = 1;
53 private const int STR_IS_ASCII = 2;
54 private const int STR_IS_CONTROL = 3;
55 private const int STR_IS_BOOL = 4;
56 private const int STR_IS_DIGIT = 5;
57 private const int STR_IS_DOUBLE = 6;
58 private const int STR_IS_FALSE = 7;
59 private const int STR_IS_GRAPH = 8;
60 private const int STR_IS_INT = 9;
61 private const int STR_IS_LOWER = 10;
62 private const int STR_IS_PRINT = 11;
63 private const int STR_IS_PUNCT = 12;
64 private const int STR_IS_SPACE = 13;
65 private const int STR_IS_TRUE = 14;
66 private const int STR_IS_UPPER = 15;
67 private const int STR_IS_WIDE = 16;
68 private const int STR_IS_WORD = 17;
69 private const int STR_IS_XDIGIT = 18;
70  
71 /// <summary> Java's Character class has a many boolean test functions to check
72 /// the kind of a character (like isLowerCase() or isISOControl()).
73 /// Unfortunately some are missing (like isPunct() or isPrint()), so
74 /// here we define bitsets to compare the result of Character.getType().
75 /// </summary>
76  
77 private static readonly int ALPHA_BITS = ( ( 1 << (byte)System.Globalization.UnicodeCategory.UppercaseLetter ) | ( 1 << (byte)System.Globalization.UnicodeCategory.LowercaseLetter ) | ( 1 << (byte)System.Globalization.UnicodeCategory.TitlecaseLetter ) | ( 1 << (byte)System.Globalization.UnicodeCategory.ModifierLetter ) | ( 1 << (byte)System.Globalization.UnicodeCategory.OtherLetter ) );
78 private static readonly int PUNCT_BITS = ( ( 1 << (byte)System.Globalization.UnicodeCategory.ConnectorPunctuation ) | ( 1 << (byte)System.Globalization.UnicodeCategory.DashPunctuation ) | ( 1 << (byte)System.Globalization.UnicodeCategory.InitialQuotePunctuation ) | ( 1 << (byte)System.Globalization.UnicodeCategory.FinalQuotePunctuation ) | ( 1 << (byte)System.Globalization.UnicodeCategory.OtherPunctuation ) );
79 private static readonly int PRINT_BITS = ( ALPHA_BITS | ( 1 << (byte)System.Globalization.UnicodeCategory.DecimalDigitNumber ) | ( 1 << (byte)System.Globalization.UnicodeCategory.SpaceSeparator ) | ( 1 << (byte)System.Globalization.UnicodeCategory.LineSeparator ) | ( 1 << (byte)System.Globalization.UnicodeCategory.ParagraphSeparator ) | ( 1 << (byte)System.Globalization.UnicodeCategory.NonSpacingMark ) | ( 1 << (byte)System.Globalization.UnicodeCategory.EnclosingMark ) | ( 1 << (byte)System.Globalization.UnicodeCategory.SpacingCombiningMark ) | ( 1 << (byte)System.Globalization.UnicodeCategory.LetterNumber ) | ( 1 << (byte)System.Globalization.UnicodeCategory.OtherNumber ) | PUNCT_BITS | ( 1 << (byte)System.Globalization.UnicodeCategory.MathSymbol ) | ( 1 << (byte)System.Globalization.UnicodeCategory.CurrencySymbol ) | ( 1 << (byte)System.Globalization.UnicodeCategory.ModifierSymbol ) | ( 1 << (byte)System.Globalization.UnicodeCategory.OtherSymbol ) );
80 private static readonly int WORD_BITS = ( ALPHA_BITS | ( 1 << (byte)System.Globalization.UnicodeCategory.DecimalDigitNumber ) | ( 1 << (byte)System.Globalization.UnicodeCategory.ConnectorPunctuation ) );
81  
82 /// <summary>----------------------------------------------------------------------
83 ///
84 /// Tcl_StringObjCmd -> StringCmd.cmdProc
85 ///
86 /// This procedure is invoked to process the "string" Tcl command.
87 /// See the user documentation for details on what it does.
88 ///
89 /// Results:
90 /// None.
91 ///
92 /// Side effects:
93 /// See the user documentation.
94 ///
95 /// ----------------------------------------------------------------------
96 /// </summary>
97  
98 public TCL.CompletionCode cmdProc( Interp interp, TclObject[] objv )
99 {
100 if ( objv.Length < 2 )
101 {
102 throw new TclNumArgsException( interp, 1, objv, "option arg ?arg ...?" );
103 }
104 int index = TclIndex.get( interp, objv[1], options, "option", 0 );
105  
106 switch ( index )
107 {
108  
109 case STR_EQUAL:
110 case STR_COMPARE:
111 {
112  
113 if ( objv.Length < 4 || objv.Length > 7 )
114 {
115 throw new TclNumArgsException( interp, 2, objv, "?-nocase? ?-length int? string1 string2" );
116 }
117  
118 bool nocase = false;
119 int reqlength = -1;
120 for ( int i = 2; i < objv.Length - 2; i++ )
121 {
122  
123 string string2 = objv[i].ToString();
124 int length2 = string2.Length;
125 if ( ( length2 > 1 ) && "-nocase".StartsWith( string2 ) )
126 {
127 nocase = true;
128 }
129 else if ( ( length2 > 1 ) && "-length".StartsWith( string2 ) )
130 {
131 if ( i + 1 >= objv.Length - 2 )
132 {
133 throw new TclNumArgsException( interp, 2, objv, "?-nocase? ?-length int? string1 string2" );
134 }
135 reqlength = TclInteger.get( interp, objv[++i] );
136 }
137 else
138 {
139 throw new TclException( interp, "bad option \"" + string2 + "\": must be -nocase or -length" );
140 }
141 }
142  
143  
144 string string1 = objv[objv.Length - 2].ToString();
145  
146 string string3 = objv[objv.Length - 1].ToString();
147 int length1 = string1.Length;
148 int length3 = string3.Length;
149  
150 // This is the min length IN BYTES of the two strings
151  
152 int length = ( length1 < length3 ) ? length1 : length3;
153  
154 int match;
155  
156 if ( reqlength == 0 )
157 {
158 // Anything matches at 0 chars, right?
159  
160 match = 0;
161 }
162 else if ( nocase || ( ( reqlength > 0 ) && ( reqlength <= length ) ) )
163 {
164 // In Java, strings are always encoded in unicode, so we do
165 // not need to worry about individual char lengths
166  
167 // Do the reqlength check again, against 0 as well for
168 // the benfit of nocase
169  
170 if ( ( reqlength > 0 ) && ( reqlength < length ) )
171 {
172 length = reqlength;
173 }
174 else if ( reqlength < 0 )
175 {
176 // The requested length is negative, so we ignore it by
177 // setting it to the longer of the two lengths.
178  
179 reqlength = ( length1 > length3 ) ? length1 : length3;
180 }
181 if ( nocase )
182 {
183 string1 = string1.ToLower();
184 string3 = string3.ToLower();
185 }
186 match = System.Globalization.CultureInfo.InvariantCulture.CompareInfo.Compare( string1, 0, length, string3, 0, length, System.Globalization.CompareOptions.Ordinal );
187 // match = string1.Substring(0, (length) - (0)).CompareTo(string3.Substring(0, (length) - (0)));
188  
189 if ( ( match == 0 ) && ( reqlength > length ) )
190 {
191 match = length1 - length3;
192 }
193 }
194 else
195 {
196 match = System.Globalization.CultureInfo.InvariantCulture.CompareInfo.Compare( string1, 0, length, string3, 0, length, System.Globalization.CompareOptions.Ordinal );
197 // ATK match = string1.Substring(0, (length) - (0)).CompareTo(string3.Substring(0, (length) - (0)));
198 if ( match == 0 )
199 {
200 match = length1 - length3;
201 }
202 }
203  
204 if ( index == STR_EQUAL )
205 {
206 interp.setResult( ( match != 0 ) ? false : true );
207 }
208 else
209 {
210 interp.setResult( ( ( match > 0 ) ? 1 : ( match < 0 ) ? -1 : 0 ) );
211 }
212 break;
213 }
214  
215  
216 case STR_FIRST:
217 {
218 if ( objv.Length < 4 || objv.Length > 5 )
219 {
220 throw new TclNumArgsException( interp, 2, objv, "subString string ?startIndex?" );
221 }
222  
223 string string1 = objv[2].ToString();
224  
225 string string2 = objv[3].ToString();
226 int length2 = string2.Length;
227  
228 int start = 0;
229  
230 if ( objv.Length == 5 )
231 {
232 // If a startIndex is specified, we will need to fast
233 // forward to that point in the string before we think
234 // about a match.
235  
236 start = Util.getIntForIndex( interp, objv[4], length2 - 1 );
237 if ( start >= length2 )
238 {
239 interp.setResult( -1 );
240 return TCL.CompletionCode.RETURN;
241 }
242 }
243  
244 if ( string1.Length == 0 )
245 {
246 interp.setResult( -1 );
247 }
248 else
249 {
250  
251 interp.setResult( string2.IndexOf( string1, start ) );
252 }
253 break;
254 }
255  
256  
257 case STR_INDEX:
258 {
259 if ( objv.Length != 4 )
260 {
261 throw new TclNumArgsException( interp, 2, objv, "string charIndex" );
262 }
263  
264  
265 string string1 = objv[2].ToString();
266 int length1 = string1.Length;
267  
268 int i = Util.getIntForIndex( interp, objv[3], length1 - 1 );
269  
270 if ( ( i >= 0 ) && ( i < length1 ) )
271 {
272 interp.setResult( string1.Substring( i, ( i + 1 ) - ( i ) ) );
273 }
274 break;
275 }
276  
277  
278 case STR_IS:
279 {
280 if ( objv.Length < 4 || objv.Length > 7 )
281 {
282 throw new TclNumArgsException( interp, 2, objv, "class ?-strict? ?-failindex var? str" );
283 }
284 index = TclIndex.get( interp, objv[2], isOptions, "class", 0 );
285  
286 bool strict = false;
287 TclObject failVarObj = null;
288  
289 if ( objv.Length != 4 )
290 {
291 for ( int i = 3; i < objv.Length - 1; i++ )
292 {
293  
294 string string2 = objv[i].ToString();
295 int length2 = string2.Length;
296 if ( ( length2 > 1 ) && "-strict".StartsWith( string2 ) )
297 {
298 strict = true;
299 }
300 else if ( ( length2 > 1 ) && "-failindex".StartsWith( string2 ) )
301 {
302 if ( i + 1 >= objv.Length - 1 )
303 {
304 throw new TclNumArgsException( interp, 3, objv, "?-strict? ?-failindex var? str" );
305 }
306 failVarObj = objv[++i];
307 }
308 else
309 {
310 throw new TclException( interp, "bad option \"" + string2 + "\": must be -strict or -failindex" );
311 }
312 }
313 }
314  
315 bool result = true;
316 int failat = 0;
317  
318 // We get the objPtr so that we can short-cut for some classes
319 // by checking the object type (int and double), but we need
320 // the string otherwise, because we don't want any conversion
321 // of type occuring (as, for example, Tcl_Get*FromObj would do
322  
323 TclObject obj = objv[objv.Length - 1];
324  
325 string string1 = obj.ToString();
326 int length1 = string1.Length;
327 if ( length1 == 0 )
328 {
329 if ( strict )
330 {
331 result = false;
332 }
333 }
334  
335 switch ( index )
336 {
337  
338 case STR_IS_BOOL:
339 case STR_IS_TRUE:
340 case STR_IS_FALSE:
341 {
342 if ( obj.InternalRep is TclBoolean )
343 {
344 if ( ( ( index == STR_IS_TRUE ) && !TclBoolean.get( interp, obj ) ) || ( ( index == STR_IS_FALSE ) && TclBoolean.get( interp, obj ) ) )
345 {
346 result = false;
347 }
348 }
349 else
350 {
351 try
352 {
353 bool i = TclBoolean.get( null, obj );
354 if ( ( ( index == STR_IS_TRUE ) && !i ) || ( ( index == STR_IS_FALSE ) && i ) )
355 {
356 result = false;
357 }
358 }
359 catch ( TclException e )
360 {
361 result = false;
362 }
363 }
364 break;
365 }
366  
367 case STR_IS_DOUBLE:
368 {
369 if ( ( obj.InternalRep is TclDouble ) || ( obj.InternalRep is TclInteger ) )
370 {
371 break;
372 }
373  
374 // This is adapted from Tcl_GetDouble
375 //
376 // The danger in this function is that
377 // "12345678901234567890" is an acceptable 'double',
378 // but will later be interp'd as an int by something
379 // like [expr]. Therefore, we check to see if it looks
380 // like an int, and if so we do a range check on it.
381 // If strtoul gets to the end, we know we either
382 // received an acceptable int, or over/underflow
383  
384 if ( Expression.looksLikeInt( string1, length1, 0 ) )
385 {
386 char c = string1[0];
387 int signIx = ( c == '-' || c == '+' ) ? 1 : 0;
388 StrtoulResult res = Util.strtoul( string1, signIx, 0 );
389 if ( res.index == length1 )
390 {
391 if ( res.errno == TCL.INTEGER_RANGE )
392 {
393 result = false;
394 failat = -1;
395 }
396 break;
397 }
398 }
399  
400 char c2 = string1[0];
401 int signIx2 = ( c2 == '-' || c2 == '+' ) ? 1 : 0;
402 StrtodResult res2 = Util.strtod( string1, signIx2 );
403 if ( res2.errno == TCL.DOUBLE_RANGE )
404 {
405 // if (errno == ERANGE), then it was an over/underflow
406 // problem, but in this method, we only want to know
407 // yes or no, so bad flow returns 0 (false) and sets
408 // the failVarObj to the string length.
409  
410 result = false;
411 failat = -1;
412 }
413 else if ( res2.index == 0 )
414 {
415 // In this case, nothing like a number was found
416  
417 result = false;
418 failat = 0;
419 }
420 else
421 {
422 // Go onto SPACE, since we are
423 // allowed trailing whitespace
424  
425 failat = res2.index;
426 for ( int i = res2.index; i < length1; i++ )
427 {
428 if ( !System.Char.IsWhiteSpace( string1[i] ) )
429 {
430 result = false;
431 break;
432 }
433 }
434 }
435 break;
436 }
437  
438 case STR_IS_INT:
439 {
440 if ( obj.InternalRep is TclInteger )
441 {
442 break;
443 }
444 bool isInteger = true;
445 try
446 {
447 TclInteger.get( null, obj );
448 }
449 catch ( TclException e )
450 {
451 isInteger = false;
452 }
453 if ( isInteger )
454 {
455 break;
456 }
457  
458 char c = string1[0];
459 int signIx = ( c == '-' || c == '+' ) ? 1 : 0;
460 StrtoulResult res = Util.strtoul( string1, signIx, 0 );
461 if ( res.errno == TCL.INTEGER_RANGE )
462 {
463 // if (errno == ERANGE), then it was an over/underflow
464 // problem, but in this method, we only want to know
465 // yes or no, so bad flow returns false and sets
466 // the failVarObj to the string length.
467  
468 result = false;
469 failat = -1;
470 }
471 else if ( res.index == 0 )
472 {
473 // In this case, nothing like a number was found
474  
475 result = false;
476 failat = 0;
477 }
478 else
479 {
480 // Go onto SPACE, since we are
481 // allowed trailing whitespace
482  
483 failat = res.index;
484 for ( int i = res.index; i < length1; i++ )
485 {
486 if ( !System.Char.IsWhiteSpace( string1[i] ) )
487 {
488 result = false;
489 break;
490 }
491 }
492 }
493 break;
494 }
495  
496 case STR_IS_WIDE:
497 {
498 if ( obj.InternalRep is TclLong )
499 {
500 break;
501 }
502 bool isInteger = true;
503 try
504 {
505 TclLong.get( null, obj );
506 }
507 catch ( TclException e )
508 {
509 isInteger = false;
510 }
511 if ( isInteger )
512 {
513 break;
514 }
515  
516 char c = string1[0];
517 int signIx = ( c == '-' || c == '+' ) ? 1 : 0;
518 StrtoulResult res = Util.strtoul( string1, signIx, 0 );
519 if ( res.errno == TCL.INTEGER_RANGE )
520 {
521 // if (errno == ERANGE), then it was an over/underflow
522 // problem, but in this method, we only want to know
523 // yes or no, so bad flow returns false and sets
524 // the failVarObj to the string length.
525  
526 result = false;
527 failat = -1;
528 }
529 else if ( res.index == 0 )
530 {
531 // In this case, nothing like a number was found
532  
533 result = false;
534 failat = 0;
535 }
536 else
537 {
538 // Go onto SPACE, since we are
539 // allowed trailing whitespace
540  
541 failat = res.index;
542 for ( int i = res.index; i < length1; i++ )
543 {
544 if ( !System.Char.IsWhiteSpace( string1[i] ) )
545 {
546 result = false;
547 break;
548 }
549 }
550 }
551 break;
552 }
553  
554 default:
555 {
556 for ( failat = 0; failat < length1; failat++ )
557 {
558 char c = string1[failat];
559 switch ( index )
560 {
561  
562 case STR_IS_ASCII:
563  
564 result = c < 0x80;
565 break;
566  
567 case STR_IS_ALNUM:
568 result = System.Char.IsLetterOrDigit( c );
569 break;
570  
571 case STR_IS_ALPHA:
572 result = System.Char.IsLetter( c );
573 break;
574  
575 case STR_IS_DIGIT:
576 result = System.Char.IsDigit( c );
577 break;
578  
579 case STR_IS_GRAPH:
580 result = ( ( 1 << (int)System.Char.GetUnicodeCategory( c ) ) & PRINT_BITS ) != 0 && c != ' ';
581 break;
582  
583 case STR_IS_PRINT:
584 result = ( ( 1 << (int)System.Char.GetUnicodeCategory( c ) ) & PRINT_BITS ) != 0;
585 break;
586  
587 case STR_IS_PUNCT:
588 result = ( ( 1 << (int)System.Char.GetUnicodeCategory( c ) ) & PUNCT_BITS ) != 0;
589 break;
590  
591 case STR_IS_UPPER:
592 result = System.Char.IsUpper( c );
593 break;
594  
595 case STR_IS_SPACE:
596 result = System.Char.IsWhiteSpace( c );
597 break;
598  
599 case STR_IS_CONTROL:
600 result = ( System.Char.GetUnicodeCategory( c ) == System.Globalization.UnicodeCategory.Control );
601 break;
602  
603 case STR_IS_LOWER:
604 result = System.Char.IsLower( c );
605 break;
606  
607 case STR_IS_WORD:
608 result = ( ( 1 << (int)System.Char.GetUnicodeCategory( c ) ) & WORD_BITS ) != 0;
609 break;
610  
611 case STR_IS_XDIGIT:
612 result = "0123456789ABCDEFabcdef".IndexOf( c ) >= 0;
613 break;
614  
615 default:
616 throw new TclRuntimeError( "unimplemented" );
617  
618 }
619 if ( !result )
620 {
621 break;
622 }
623 }
624 }
625 break;
626  
627 }
628  
629 // Only set the failVarObj when we will return 0
630 // and we have indicated a valid fail index (>= 0)
631  
632 if ( ( !result ) && ( failVarObj != null ) )
633 {
634 interp.setVar( failVarObj, TclInteger.newInstance( failat ), 0 );
635 }
636 interp.setResult( result );
637 break;
638 }
639  
640  
641 case STR_LAST:
642 {
643 if ( objv.Length < 4 || objv.Length > 5 )
644 {
645 throw new TclNumArgsException( interp, 2, objv, "subString string ?startIndex?" );
646 }
647  
648 string string1 = objv[2].ToString();
649  
650 string string2 = objv[3].ToString();
651 int length2 = string2.Length;
652  
653 int start = 0;
654 if ( objv.Length == 5 )
655 {
656 // If a startIndex is specified, we will need to fast
657 // forward to that point in the string before we think
658 // about a match.
659  
660 start = Util.getIntForIndex( interp, objv[4], length2 - 1 );
661 if ( start < 0 )
662 {
663 interp.setResult( -1 );
664 break;
665 }
666 else if ( start < length2 )
667 {
668 string2 = string2.Substring( 0, ( start + 1 ) - ( 0 ) );
669 }
670 }
671  
672 if ( string1.Length == 0 )
673 {
674 interp.setResult( -1 );
675 }
676 else
677 {
678 interp.setResult( string2.LastIndexOf( string1 ) );
679 }
680 break;
681 }
682  
683  
684 case STR_BYTELENGTH:
685 if ( objv.Length != 3 )
686 {
687 throw new TclNumArgsException( interp, 2, objv, "string" );
688 }
689  
690 interp.setResult( Utf8Count( objv[2].ToString() ) );
691 break;
692  
693  
694 case STR_LENGTH:
695 {
696 if ( objv.Length != 3 )
697 {
698 throw new TclNumArgsException( interp, 2, objv, "string" );
699 }
700  
701 interp.setResult( objv[2].ToString().Length );
702 break;
703 }
704  
705  
706 case STR_MAP:
707 {
708 if ( objv.Length < 4 || objv.Length > 5 )
709 {
710 throw new TclNumArgsException( interp, 2, objv, "?-nocase? charMap string" );
711 }
712  
713 bool nocase = false;
714 if ( objv.Length == 5 )
715 {
716  
717 string string2 = objv[2].ToString();
718 int length2 = string2.Length;
719 if ( ( length2 > 1 ) && "-nocase".StartsWith( string2 ) )
720 {
721 nocase = true;
722 }
723 else
724 {
725 throw new TclException( interp, "bad option \"" + string2 + "\": must be -nocase" );
726 }
727 }
728  
729 TclObject[] mapElemv = TclList.getElements( interp, objv[objv.Length - 2] );
730 if ( mapElemv.Length == 0 )
731 {
732 // empty charMap, just return whatever string was given
733  
734 interp.setResult( objv[objv.Length - 1] );
735 }
736 else if ( ( mapElemv.Length % 2 ) != 0 )
737 {
738 // The charMap must be an even number of key/value items
739  
740 throw new TclException( interp, "char map list unbalanced" );
741 }
742  
743 string string1 = objv[objv.Length - 1].ToString();
744 string cmpString1;
745 if ( nocase )
746 {
747 cmpString1 = string1.ToLower();
748 }
749 else
750 {
751 cmpString1 = string1;
752 }
753 int length1 = string1.Length;
754 if ( length1 == 0 )
755 {
756 // Empty input string, just stop now
757  
758 break;
759 }
760  
761 // Precompute pointers to the unicode string and length.
762 // This saves us repeated function calls later,
763 // significantly speeding up the algorithm.
764  
765 string[] mapStrings = new string[mapElemv.Length];
766 int[] mapLens = new int[mapElemv.Length];
767 for ( int ix = 0; ix < mapElemv.Length; ix++ )
768 {
769  
770 mapStrings[ix] = mapElemv[ix].ToString();
771 mapLens[ix] = mapStrings[ix].Length;
772 }
773 string[] cmpStrings;
774 if ( nocase )
775 {
776 cmpStrings = new string[mapStrings.Length];
777 for ( int ix = 0; ix < mapStrings.Length; ix++ )
778 {
779 cmpStrings[ix] = mapStrings[ix].ToLower();
780 }
781 }
782 else
783 {
784 cmpStrings = mapStrings;
785 }
786  
787 TclObject result = TclString.newInstance( "" );
788 int p, str1;
789 for ( p = 0, str1 = 0; str1 < length1; str1++ )
790 {
791 for ( index = 0; index < mapStrings.Length; index += 2 )
792 {
793 // Get the key string to match on
794  
795 string string2 = mapStrings[index];
796 int length2 = mapLens[index];
797 if ( ( length2 > 0 ) && ( cmpString1.Substring( str1 ).StartsWith( cmpStrings[index] ) ) )
798 {
799 if ( p != str1 )
800 {
801 // Put the skipped chars onto the result first
802  
803 TclString.append( result, string1.Substring( p, ( str1 ) - ( p ) ) );
804 p = str1 + length2;
805 }
806 else
807 {
808 p += length2;
809 }
810  
811 // Adjust len to be full length of matched string
812  
813 str1 = p - 1;
814  
815 // Append the map value to the unicode string
816  
817 TclString.append( result, mapStrings[index + 1] );
818 break;
819 }
820 }
821 }
822  
823 if ( p != str1 )
824 {
825 // Put the rest of the unmapped chars onto result
826  
827 TclString.append( result, string1.Substring( p, ( str1 ) - ( p ) ) );
828 }
829 interp.setResult( result );
830 break;
831 }
832  
833  
834 case STR_MATCH:
835 {
836 if ( objv.Length < 4 || objv.Length > 5 )
837 {
838 throw new TclNumArgsException( interp, 2, objv, "?-nocase? pattern string" );
839 }
840  
841 string string1, string2;
842 if ( objv.Length == 5 )
843 {
844  
845 string inString = objv[2].ToString();
846 if ( !( ( inString.Length > 1 ) && "-nocase".StartsWith( inString ) ) )
847 {
848 throw new TclException( interp, "bad option \"" + inString + "\": must be -nocase" );
849 }
850  
851 string1 = objv[4].ToString().ToLower();
852  
853 string2 = objv[3].ToString().ToLower();
854 }
855 else
856 {
857  
858 string1 = objv[3].ToString();
859  
860 string2 = objv[2].ToString();
861 }
862  
863 interp.setResult( Util.stringMatch( string1, string2 ) );
864 break;
865 }
866  
867  
868 case STR_RANGE:
869 {
870 if ( objv.Length != 5 )
871 {
872 throw new TclNumArgsException( interp, 2, objv, "string first last" );
873 }
874  
875  
876 string string1 = objv[2].ToString();
877 int length1 = string1.Length;
878  
879 int first = Util.getIntForIndex( interp, objv[3], length1 - 1 );
880 if ( first < 0 )
881 {
882 first = 0;
883 }
884 int last = Util.getIntForIndex( interp, objv[4], length1 - 1 );
885 if ( last >= length1 )
886 {
887 last = length1 - 1;
888 }
889  
890 if ( first > last )
891 {
892 interp.resetResult();
893 }
894 else
895 {
896 interp.setResult( string1.Substring( first, ( last + 1 ) - ( first ) ) );
897 }
898 break;
899 }
900  
901  
902 case STR_REPEAT:
903 {
904 if ( objv.Length != 4 )
905 {
906 throw new TclNumArgsException( interp, 2, objv, "string count" );
907 }
908  
909 int count = TclInteger.get( interp, objv[3] );
910  
911  
912 string string1 = objv[2].ToString();
913 if ( string1.Length > 0 )
914 {
915 TclObject tstr = TclString.newInstance( "" );
916 for ( index = 0; index < count; index++ )
917 {
918 TclString.append( tstr, string1 );
919 }
920 interp.setResult( tstr );
921 }
922 break;
923 }
924  
925  
926 case STR_REPLACE:
927 {
928 if ( objv.Length < 5 || objv.Length > 6 )
929 {
930 throw new TclNumArgsException( interp, 2, objv, "string first last ?string?" );
931 }
932  
933  
934 string string1 = objv[2].ToString();
935 int length1 = string1.Length - 1;
936  
937 int first = Util.getIntForIndex( interp, objv[3], length1 );
938 int last = Util.getIntForIndex( interp, objv[4], length1 );
939  
940 if ( ( last < first ) || ( first > length1 ) || ( last < 0 ) )
941 {
942 interp.setResult( objv[2] );
943 }
944 else
945 {
946 if ( first < 0 )
947 {
948 first = 0;
949 }
950 string start = string1.Substring( first );
951 int ind = ( ( last > length1 ) ? length1 : last ) - first + 1;
952 string end;
953 if ( ind <= 0 )
954 {
955 end = start;
956 }
957 else if ( ind >= start.Length )
958 {
959 end = "";
960 }
961 else
962 {
963 end = start.Substring( ind );
964 }
965  
966 TclObject tstr = TclString.newInstance( string1.Substring( 0, ( first ) - ( 0 ) ) );
967  
968 if ( objv.Length == 6 )
969 {
970 TclString.append( tstr, objv[5] );
971 }
972 if ( last < length1 )
973 {
974 TclString.append( tstr, end );
975 }
976  
977 interp.setResult( tstr );
978 }
979 break;
980 }
981  
982  
983 case STR_TOLOWER:
984 case STR_TOUPPER:
985 case STR_TOTITLE:
986 {
987 if ( objv.Length < 3 || objv.Length > 5 )
988 {
989 throw new TclNumArgsException( interp, 2, objv, "string ?first? ?last?" );
990 }
991  
992 string string1 = objv[2].ToString();
993  
994 if ( objv.Length == 3 )
995 {
996 if ( index == STR_TOLOWER )
997 {
998 interp.setResult( string1.ToLower() );
999 }
1000 else if ( index == STR_TOUPPER )
1001 {
1002 interp.setResult( string1.ToUpper() );
1003 }
1004 else
1005 {
1006 interp.setResult( Util.toTitle( string1 ) );
1007 }
1008 }
1009 else
1010 {
1011 int length1 = string1.Length - 1;
1012 int first = Util.getIntForIndex( interp, objv[3], length1 );
1013 if ( first < 0 )
1014 {
1015 first = 0;
1016 }
1017 int last = first;
1018 if ( objv.Length == 5 )
1019 {
1020 last = Util.getIntForIndex( interp, objv[4], length1 );
1021 }
1022 if ( last >= length1 )
1023 {
1024 last = length1;
1025 }
1026 if ( last < first )
1027 {
1028 interp.setResult( objv[2] );
1029 break;
1030 }
1031  
1032 string string2;
1033 StringBuilder buf = new StringBuilder();
1034 buf.Append( string1.Substring( 0, ( first ) - ( 0 ) ) );
1035 if ( last + 1 > length1 )
1036 {
1037 string2 = string1.Substring( first );
1038 }
1039 else
1040 {
1041 string2 = string1.Substring( first, ( last + 1 ) - ( first ) );
1042 }
1043 if ( index == STR_TOLOWER )
1044 {
1045 buf.Append( string2.ToLower() );
1046 }
1047 else if ( index == STR_TOUPPER )
1048 {
1049 buf.Append( string2.ToUpper() );
1050 }
1051 else
1052 {
1053 buf.Append( Util.toTitle( string2 ) );
1054 }
1055 if ( last + 1 <= length1 )
1056 {
1057 buf.Append( string1.Substring( last + 1 ) );
1058 }
1059  
1060 interp.setResult( buf.ToString() );
1061 }
1062 break;
1063 }
1064  
1065  
1066 case STR_TRIM:
1067 {
1068 if ( objv.Length == 3 )
1069 {
1070 // Case 1: "string trim str" --
1071 // Remove leading and trailing white space
1072  
1073  
1074 interp.setResult( objv[2].ToString().Trim() );
1075 }
1076 else if ( objv.Length == 4 )
1077 {
1078  
1079 // Case 2: "string trim str chars" --
1080 // Remove leading and trailing chars in the chars set
1081  
1082  
1083 string tmp = Util.TrimLeft( objv[2].ToString(), objv[3].ToString() );
1084  
1085 interp.setResult( Util.TrimRight( tmp, objv[3].ToString() ) );
1086 }
1087 else
1088 {
1089 // Case 3: Wrong # of args
1090  
1091 throw new TclNumArgsException( interp, 2, objv, "string ?chars?" );
1092 }
1093 break;
1094 }
1095  
1096  
1097 case STR_TRIMLEFT:
1098 {
1099 if ( objv.Length == 3 )
1100 {
1101 // Case 1: "string trimleft str" --
1102 // Remove leading and trailing white space
1103  
1104  
1105 interp.setResult( Util.TrimLeft( objv[2].ToString() ) );
1106 }
1107 else if ( objv.Length == 4 )
1108 {
1109 // Case 2: "string trimleft str chars" --
1110 // Remove leading and trailing chars in the chars set
1111  
1112  
1113 interp.setResult( Util.TrimLeft( objv[2].ToString(), objv[3].ToString() ) );
1114 }
1115 else
1116 {
1117 // Case 3: Wrong # of args
1118  
1119 throw new TclNumArgsException( interp, 2, objv, "string ?chars?" );
1120 }
1121 break;
1122 }
1123  
1124  
1125 case STR_TRIMRIGHT:
1126 {
1127 if ( objv.Length == 3 )
1128 {
1129 // Case 1: "string trimright str" --
1130 // Remove leading and trailing white space
1131  
1132  
1133 interp.setResult( Util.TrimRight( objv[2].ToString() ) );
1134 }
1135 else if ( objv.Length == 4 )
1136 {
1137 // Case 2: "string trimright str chars" --
1138 // Remove leading and trailing chars in the chars set
1139  
1140  
1141 interp.setResult( Util.TrimRight( objv[2].ToString(), objv[3].ToString() ) );
1142 }
1143 else
1144 {
1145 // Case 3: Wrong # of args
1146  
1147 throw new TclNumArgsException( interp, 2, objv, "string ?chars?" );
1148 }
1149 break;
1150 }
1151  
1152  
1153 case STR_WORDEND:
1154 {
1155 if ( objv.Length != 4 )
1156 {
1157 throw new TclNumArgsException( interp, 2, objv, "string index" );
1158 }
1159  
1160  
1161 string string1 = objv[2].ToString();
1162 char[] strArray = string1.ToCharArray();
1163 int cur;
1164 int length1 = string1.Length;
1165 index = Util.getIntForIndex( interp, objv[3], length1 - 1 );
1166  
1167 if ( index < 0 )
1168 {
1169 index = 0;
1170 }
1171 if ( index >= length1 )
1172 {
1173 interp.setResult( length1 );
1174 return TCL.CompletionCode.RETURN;
1175 }
1176 for ( cur = index; cur < length1; cur++ )
1177 {
1178 char c = strArray[cur];
1179 if ( ( ( 1 << (int)System.Char.GetUnicodeCategory( c ) ) & WORD_BITS ) == 0 )
1180 {
1181 break;
1182 }
1183 }
1184 if ( cur == index )
1185 {
1186 cur = index + 1;
1187 }
1188 interp.setResult( cur );
1189 break;
1190 }
1191  
1192  
1193 case STR_WORDSTART:
1194 {
1195 if ( objv.Length != 4 )
1196 {
1197 throw new TclNumArgsException( interp, 2, objv, "string index" );
1198 }
1199  
1200  
1201 string string1 = objv[2].ToString();
1202 char[] strArray = string1.ToCharArray();
1203 int cur;
1204 int length1 = string1.Length;
1205 index = Util.getIntForIndex( interp, objv[3], length1 - 1 );
1206  
1207 if ( index > length1 )
1208 {
1209 index = length1 - 1;
1210 }
1211 if ( index < 0 )
1212 {
1213 interp.setResult( 0 );
1214 return TCL.CompletionCode.RETURN;
1215 }
1216 for ( cur = index; cur >= 0; cur-- )
1217 {
1218 char c = strArray[cur];
1219 if ( ( ( 1 << (int)System.Char.GetUnicodeCategory( c ) ) & WORD_BITS ) == 0 )
1220 {
1221 break;
1222 }
1223 }
1224 if ( cur != index )
1225 {
1226 cur += 1;
1227 }
1228 interp.setResult( cur );
1229 break;
1230 }
1231 }
1232 return TCL.CompletionCode.RETURN;
1233 }
1234  
1235 // return the number of Utf8 bytes that would be needed to store s
1236  
1237 private int Utf8Count( string s )
1238 {
1239 int p = 0;
1240 int len = s.Length;
1241 char c;
1242 int sum = 0;
1243  
1244 while ( p < len )
1245 {
1246 c = s[p++];
1247  
1248 if ( ( c > 0 ) && ( c < 0x80 ) )
1249 {
1250 sum += 1;
1251 continue;
1252 }
1253 if ( c <= 0x7FF )
1254 {
1255 sum += 2;
1256 continue;
1257 }
1258 if ( c <= 0xFFFF )
1259 {
1260 sum += 3;
1261 continue;
1262 }
1263 }
1264  
1265 return sum;
1266 }
1267 } // end StringCmd
1268 }