/* * StringCmd.java * * Copyright (c) 1997 Cornell University. * Copyright (c) 1997 Sun Microsystems, Inc. * Copyright (c) 1998-2000 Scriptics Corporation. * Copyright (c) 2000 Christian Krone. * * See the file "license.terms" for information on usage and * redistribution of this file, and for a DISCLAIMER OF ALL * WARRANTIES. * * Included in SQLite3 port to C# for use in testharness only; 2008 Noah B Hart * * RCS @(#) $Id: StringCmd.java,v 1.4 2000/08/20 08:37:47 mo Exp $ * */ using System.Text; namespace tcl.lang { /// This class implements the built-in "string" command in Tcl. class StringCmd : Command { 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" }; private const int STR_BYTELENGTH = 0; private const int STR_COMPARE = 1; private const int STR_EQUAL = 2; private const int STR_FIRST = 3; private const int STR_INDEX = 4; private const int STR_IS = 5; private const int STR_LAST = 6; private const int STR_LENGTH = 7; private const int STR_MAP = 8; private const int STR_MATCH = 9; private const int STR_RANGE = 10; private const int STR_REPEAT = 11; private const int STR_REPLACE = 12; private const int STR_TOLOWER = 13; private const int STR_TOUPPER = 14; private const int STR_TOTITLE = 15; private const int STR_TRIM = 16; private const int STR_TRIMLEFT = 17; private const int STR_TRIMRIGHT = 18; private const int STR_WORDEND = 19; private const int STR_WORDSTART = 20; 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" }; private const int STR_IS_ALNUM = 0; private const int STR_IS_ALPHA = 1; private const int STR_IS_ASCII = 2; private const int STR_IS_CONTROL = 3; private const int STR_IS_BOOL = 4; private const int STR_IS_DIGIT = 5; private const int STR_IS_DOUBLE = 6; private const int STR_IS_FALSE = 7; private const int STR_IS_GRAPH = 8; private const int STR_IS_INT = 9; private const int STR_IS_LOWER = 10; private const int STR_IS_PRINT = 11; private const int STR_IS_PUNCT = 12; private const int STR_IS_SPACE = 13; private const int STR_IS_TRUE = 14; private const int STR_IS_UPPER = 15; private const int STR_IS_WIDE = 16; private const int STR_IS_WORD = 17; private const int STR_IS_XDIGIT = 18; /// Java's Character class has a many boolean test functions to check /// the kind of a character (like isLowerCase() or isISOControl()). /// Unfortunately some are missing (like isPunct() or isPrint()), so /// here we define bitsets to compare the result of Character.getType(). /// 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 ) ); 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 ) ); 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 ) ); private static readonly int WORD_BITS = ( ALPHA_BITS | ( 1 << (byte)System.Globalization.UnicodeCategory.DecimalDigitNumber ) | ( 1 << (byte)System.Globalization.UnicodeCategory.ConnectorPunctuation ) ); /// ---------------------------------------------------------------------- /// /// Tcl_StringObjCmd -> StringCmd.cmdProc /// /// This procedure is invoked to process the "string" Tcl command. /// See the user documentation for details on what it does. /// /// Results: /// None. /// /// Side effects: /// See the user documentation. /// /// ---------------------------------------------------------------------- /// public TCL.CompletionCode cmdProc( Interp interp, TclObject[] objv ) { if ( objv.Length < 2 ) { throw new TclNumArgsException( interp, 1, objv, "option arg ?arg ...?" ); } int index = TclIndex.get( interp, objv[1], options, "option", 0 ); switch ( index ) { case STR_EQUAL: case STR_COMPARE: { if ( objv.Length < 4 || objv.Length > 7 ) { throw new TclNumArgsException( interp, 2, objv, "?-nocase? ?-length int? string1 string2" ); } bool nocase = false; int reqlength = -1; for ( int i = 2; i < objv.Length - 2; i++ ) { string string2 = objv[i].ToString(); int length2 = string2.Length; if ( ( length2 > 1 ) && "-nocase".StartsWith( string2 ) ) { nocase = true; } else if ( ( length2 > 1 ) && "-length".StartsWith( string2 ) ) { if ( i + 1 >= objv.Length - 2 ) { throw new TclNumArgsException( interp, 2, objv, "?-nocase? ?-length int? string1 string2" ); } reqlength = TclInteger.get( interp, objv[++i] ); } else { throw new TclException( interp, "bad option \"" + string2 + "\": must be -nocase or -length" ); } } string string1 = objv[objv.Length - 2].ToString(); string string3 = objv[objv.Length - 1].ToString(); int length1 = string1.Length; int length3 = string3.Length; // This is the min length IN BYTES of the two strings int length = ( length1 < length3 ) ? length1 : length3; int match; if ( reqlength == 0 ) { // Anything matches at 0 chars, right? match = 0; } else if ( nocase || ( ( reqlength > 0 ) && ( reqlength <= length ) ) ) { // In Java, strings are always encoded in unicode, so we do // not need to worry about individual char lengths // Do the reqlength check again, against 0 as well for // the benfit of nocase if ( ( reqlength > 0 ) && ( reqlength < length ) ) { length = reqlength; } else if ( reqlength < 0 ) { // The requested length is negative, so we ignore it by // setting it to the longer of the two lengths. reqlength = ( length1 > length3 ) ? length1 : length3; } if ( nocase ) { string1 = string1.ToLower(); string3 = string3.ToLower(); } match = System.Globalization.CultureInfo.InvariantCulture.CompareInfo.Compare( string1, 0, length, string3, 0, length, System.Globalization.CompareOptions.Ordinal ); // match = string1.Substring(0, (length) - (0)).CompareTo(string3.Substring(0, (length) - (0))); if ( ( match == 0 ) && ( reqlength > length ) ) { match = length1 - length3; } } else { match = System.Globalization.CultureInfo.InvariantCulture.CompareInfo.Compare( string1, 0, length, string3, 0, length, System.Globalization.CompareOptions.Ordinal ); // ATK match = string1.Substring(0, (length) - (0)).CompareTo(string3.Substring(0, (length) - (0))); if ( match == 0 ) { match = length1 - length3; } } if ( index == STR_EQUAL ) { interp.setResult( ( match != 0 ) ? false : true ); } else { interp.setResult( ( ( match > 0 ) ? 1 : ( match < 0 ) ? -1 : 0 ) ); } break; } case STR_FIRST: { if ( objv.Length < 4 || objv.Length > 5 ) { throw new TclNumArgsException( interp, 2, objv, "subString string ?startIndex?" ); } string string1 = objv[2].ToString(); string string2 = objv[3].ToString(); int length2 = string2.Length; int start = 0; if ( objv.Length == 5 ) { // If a startIndex is specified, we will need to fast // forward to that point in the string before we think // about a match. start = Util.getIntForIndex( interp, objv[4], length2 - 1 ); if ( start >= length2 ) { interp.setResult( -1 ); return TCL.CompletionCode.RETURN; } } if ( string1.Length == 0 ) { interp.setResult( -1 ); } else { interp.setResult( string2.IndexOf( string1, start ) ); } break; } case STR_INDEX: { if ( objv.Length != 4 ) { throw new TclNumArgsException( interp, 2, objv, "string charIndex" ); } string string1 = objv[2].ToString(); int length1 = string1.Length; int i = Util.getIntForIndex( interp, objv[3], length1 - 1 ); if ( ( i >= 0 ) && ( i < length1 ) ) { interp.setResult( string1.Substring( i, ( i + 1 ) - ( i ) ) ); } break; } case STR_IS: { if ( objv.Length < 4 || objv.Length > 7 ) { throw new TclNumArgsException( interp, 2, objv, "class ?-strict? ?-failindex var? str" ); } index = TclIndex.get( interp, objv[2], isOptions, "class", 0 ); bool strict = false; TclObject failVarObj = null; if ( objv.Length != 4 ) { for ( int i = 3; i < objv.Length - 1; i++ ) { string string2 = objv[i].ToString(); int length2 = string2.Length; if ( ( length2 > 1 ) && "-strict".StartsWith( string2 ) ) { strict = true; } else if ( ( length2 > 1 ) && "-failindex".StartsWith( string2 ) ) { if ( i + 1 >= objv.Length - 1 ) { throw new TclNumArgsException( interp, 3, objv, "?-strict? ?-failindex var? str" ); } failVarObj = objv[++i]; } else { throw new TclException( interp, "bad option \"" + string2 + "\": must be -strict or -failindex" ); } } } bool result = true; int failat = 0; // We get the objPtr so that we can short-cut for some classes // by checking the object type (int and double), but we need // the string otherwise, because we don't want any conversion // of type occuring (as, for example, Tcl_Get*FromObj would do TclObject obj = objv[objv.Length - 1]; string string1 = obj.ToString(); int length1 = string1.Length; if ( length1 == 0 ) { if ( strict ) { result = false; } } switch ( index ) { case STR_IS_BOOL: case STR_IS_TRUE: case STR_IS_FALSE: { if ( obj.InternalRep is TclBoolean ) { if ( ( ( index == STR_IS_TRUE ) && !TclBoolean.get( interp, obj ) ) || ( ( index == STR_IS_FALSE ) && TclBoolean.get( interp, obj ) ) ) { result = false; } } else { try { bool i = TclBoolean.get( null, obj ); if ( ( ( index == STR_IS_TRUE ) && !i ) || ( ( index == STR_IS_FALSE ) && i ) ) { result = false; } } catch ( TclException e ) { result = false; } } break; } case STR_IS_DOUBLE: { if ( ( obj.InternalRep is TclDouble ) || ( obj.InternalRep is TclInteger ) ) { break; } // This is adapted from Tcl_GetDouble // // The danger in this function is that // "12345678901234567890" is an acceptable 'double', // but will later be interp'd as an int by something // like [expr]. Therefore, we check to see if it looks // like an int, and if so we do a range check on it. // If strtoul gets to the end, we know we either // received an acceptable int, or over/underflow if ( Expression.looksLikeInt( string1, length1, 0 ) ) { char c = string1[0]; int signIx = ( c == '-' || c == '+' ) ? 1 : 0; StrtoulResult res = Util.strtoul( string1, signIx, 0 ); if ( res.index == length1 ) { if ( res.errno == TCL.INTEGER_RANGE ) { result = false; failat = -1; } break; } } char c2 = string1[0]; int signIx2 = ( c2 == '-' || c2 == '+' ) ? 1 : 0; StrtodResult res2 = Util.strtod( string1, signIx2 ); if ( res2.errno == TCL.DOUBLE_RANGE ) { // if (errno == ERANGE), then it was an over/underflow // problem, but in this method, we only want to know // yes or no, so bad flow returns 0 (false) and sets // the failVarObj to the string length. result = false; failat = -1; } else if ( res2.index == 0 ) { // In this case, nothing like a number was found result = false; failat = 0; } else { // Go onto SPACE, since we are // allowed trailing whitespace failat = res2.index; for ( int i = res2.index; i < length1; i++ ) { if ( !System.Char.IsWhiteSpace( string1[i] ) ) { result = false; break; } } } break; } case STR_IS_INT: { if ( obj.InternalRep is TclInteger ) { break; } bool isInteger = true; try { TclInteger.get( null, obj ); } catch ( TclException e ) { isInteger = false; } if ( isInteger ) { break; } char c = string1[0]; int signIx = ( c == '-' || c == '+' ) ? 1 : 0; StrtoulResult res = Util.strtoul( string1, signIx, 0 ); if ( res.errno == TCL.INTEGER_RANGE ) { // if (errno == ERANGE), then it was an over/underflow // problem, but in this method, we only want to know // yes or no, so bad flow returns false and sets // the failVarObj to the string length. result = false; failat = -1; } else if ( res.index == 0 ) { // In this case, nothing like a number was found result = false; failat = 0; } else { // Go onto SPACE, since we are // allowed trailing whitespace failat = res.index; for ( int i = res.index; i < length1; i++ ) { if ( !System.Char.IsWhiteSpace( string1[i] ) ) { result = false; break; } } } break; } case STR_IS_WIDE: { if ( obj.InternalRep is TclLong ) { break; } bool isInteger = true; try { TclLong.get( null, obj ); } catch ( TclException e ) { isInteger = false; } if ( isInteger ) { break; } char c = string1[0]; int signIx = ( c == '-' || c == '+' ) ? 1 : 0; StrtoulResult res = Util.strtoul( string1, signIx, 0 ); if ( res.errno == TCL.INTEGER_RANGE ) { // if (errno == ERANGE), then it was an over/underflow // problem, but in this method, we only want to know // yes or no, so bad flow returns false and sets // the failVarObj to the string length. result = false; failat = -1; } else if ( res.index == 0 ) { // In this case, nothing like a number was found result = false; failat = 0; } else { // Go onto SPACE, since we are // allowed trailing whitespace failat = res.index; for ( int i = res.index; i < length1; i++ ) { if ( !System.Char.IsWhiteSpace( string1[i] ) ) { result = false; break; } } } break; } default: { for ( failat = 0; failat < length1; failat++ ) { char c = string1[failat]; switch ( index ) { case STR_IS_ASCII: result = c < 0x80; break; case STR_IS_ALNUM: result = System.Char.IsLetterOrDigit( c ); break; case STR_IS_ALPHA: result = System.Char.IsLetter( c ); break; case STR_IS_DIGIT: result = System.Char.IsDigit( c ); break; case STR_IS_GRAPH: result = ( ( 1 << (int)System.Char.GetUnicodeCategory( c ) ) & PRINT_BITS ) != 0 && c != ' '; break; case STR_IS_PRINT: result = ( ( 1 << (int)System.Char.GetUnicodeCategory( c ) ) & PRINT_BITS ) != 0; break; case STR_IS_PUNCT: result = ( ( 1 << (int)System.Char.GetUnicodeCategory( c ) ) & PUNCT_BITS ) != 0; break; case STR_IS_UPPER: result = System.Char.IsUpper( c ); break; case STR_IS_SPACE: result = System.Char.IsWhiteSpace( c ); break; case STR_IS_CONTROL: result = ( System.Char.GetUnicodeCategory( c ) == System.Globalization.UnicodeCategory.Control ); break; case STR_IS_LOWER: result = System.Char.IsLower( c ); break; case STR_IS_WORD: result = ( ( 1 << (int)System.Char.GetUnicodeCategory( c ) ) & WORD_BITS ) != 0; break; case STR_IS_XDIGIT: result = "0123456789ABCDEFabcdef".IndexOf( c ) >= 0; break; default: throw new TclRuntimeError( "unimplemented" ); } if ( !result ) { break; } } } break; } // Only set the failVarObj when we will return 0 // and we have indicated a valid fail index (>= 0) if ( ( !result ) && ( failVarObj != null ) ) { interp.setVar( failVarObj, TclInteger.newInstance( failat ), 0 ); } interp.setResult( result ); break; } case STR_LAST: { if ( objv.Length < 4 || objv.Length > 5 ) { throw new TclNumArgsException( interp, 2, objv, "subString string ?startIndex?" ); } string string1 = objv[2].ToString(); string string2 = objv[3].ToString(); int length2 = string2.Length; int start = 0; if ( objv.Length == 5 ) { // If a startIndex is specified, we will need to fast // forward to that point in the string before we think // about a match. start = Util.getIntForIndex( interp, objv[4], length2 - 1 ); if ( start < 0 ) { interp.setResult( -1 ); break; } else if ( start < length2 ) { string2 = string2.Substring( 0, ( start + 1 ) - ( 0 ) ); } } if ( string1.Length == 0 ) { interp.setResult( -1 ); } else { interp.setResult( string2.LastIndexOf( string1 ) ); } break; } case STR_BYTELENGTH: if ( objv.Length != 3 ) { throw new TclNumArgsException( interp, 2, objv, "string" ); } interp.setResult( Utf8Count( objv[2].ToString() ) ); break; case STR_LENGTH: { if ( objv.Length != 3 ) { throw new TclNumArgsException( interp, 2, objv, "string" ); } interp.setResult( objv[2].ToString().Length ); break; } case STR_MAP: { if ( objv.Length < 4 || objv.Length > 5 ) { throw new TclNumArgsException( interp, 2, objv, "?-nocase? charMap string" ); } bool nocase = false; if ( objv.Length == 5 ) { string string2 = objv[2].ToString(); int length2 = string2.Length; if ( ( length2 > 1 ) && "-nocase".StartsWith( string2 ) ) { nocase = true; } else { throw new TclException( interp, "bad option \"" + string2 + "\": must be -nocase" ); } } TclObject[] mapElemv = TclList.getElements( interp, objv[objv.Length - 2] ); if ( mapElemv.Length == 0 ) { // empty charMap, just return whatever string was given interp.setResult( objv[objv.Length - 1] ); } else if ( ( mapElemv.Length % 2 ) != 0 ) { // The charMap must be an even number of key/value items throw new TclException( interp, "char map list unbalanced" ); } string string1 = objv[objv.Length - 1].ToString(); string cmpString1; if ( nocase ) { cmpString1 = string1.ToLower(); } else { cmpString1 = string1; } int length1 = string1.Length; if ( length1 == 0 ) { // Empty input string, just stop now break; } // Precompute pointers to the unicode string and length. // This saves us repeated function calls later, // significantly speeding up the algorithm. string[] mapStrings = new string[mapElemv.Length]; int[] mapLens = new int[mapElemv.Length]; for ( int ix = 0; ix < mapElemv.Length; ix++ ) { mapStrings[ix] = mapElemv[ix].ToString(); mapLens[ix] = mapStrings[ix].Length; } string[] cmpStrings; if ( nocase ) { cmpStrings = new string[mapStrings.Length]; for ( int ix = 0; ix < mapStrings.Length; ix++ ) { cmpStrings[ix] = mapStrings[ix].ToLower(); } } else { cmpStrings = mapStrings; } TclObject result = TclString.newInstance( "" ); int p, str1; for ( p = 0, str1 = 0; str1 < length1; str1++ ) { for ( index = 0; index < mapStrings.Length; index += 2 ) { // Get the key string to match on string string2 = mapStrings[index]; int length2 = mapLens[index]; if ( ( length2 > 0 ) && ( cmpString1.Substring( str1 ).StartsWith( cmpStrings[index] ) ) ) { if ( p != str1 ) { // Put the skipped chars onto the result first TclString.append( result, string1.Substring( p, ( str1 ) - ( p ) ) ); p = str1 + length2; } else { p += length2; } // Adjust len to be full length of matched string str1 = p - 1; // Append the map value to the unicode string TclString.append( result, mapStrings[index + 1] ); break; } } } if ( p != str1 ) { // Put the rest of the unmapped chars onto result TclString.append( result, string1.Substring( p, ( str1 ) - ( p ) ) ); } interp.setResult( result ); break; } case STR_MATCH: { if ( objv.Length < 4 || objv.Length > 5 ) { throw new TclNumArgsException( interp, 2, objv, "?-nocase? pattern string" ); } string string1, string2; if ( objv.Length == 5 ) { string inString = objv[2].ToString(); if ( !( ( inString.Length > 1 ) && "-nocase".StartsWith( inString ) ) ) { throw new TclException( interp, "bad option \"" + inString + "\": must be -nocase" ); } string1 = objv[4].ToString().ToLower(); string2 = objv[3].ToString().ToLower(); } else { string1 = objv[3].ToString(); string2 = objv[2].ToString(); } interp.setResult( Util.stringMatch( string1, string2 ) ); break; } case STR_RANGE: { if ( objv.Length != 5 ) { throw new TclNumArgsException( interp, 2, objv, "string first last" ); } string string1 = objv[2].ToString(); int length1 = string1.Length; int first = Util.getIntForIndex( interp, objv[3], length1 - 1 ); if ( first < 0 ) { first = 0; } int last = Util.getIntForIndex( interp, objv[4], length1 - 1 ); if ( last >= length1 ) { last = length1 - 1; } if ( first > last ) { interp.resetResult(); } else { interp.setResult( string1.Substring( first, ( last + 1 ) - ( first ) ) ); } break; } case STR_REPEAT: { if ( objv.Length != 4 ) { throw new TclNumArgsException( interp, 2, objv, "string count" ); } int count = TclInteger.get( interp, objv[3] ); string string1 = objv[2].ToString(); if ( string1.Length > 0 ) { TclObject tstr = TclString.newInstance( "" ); for ( index = 0; index < count; index++ ) { TclString.append( tstr, string1 ); } interp.setResult( tstr ); } break; } case STR_REPLACE: { if ( objv.Length < 5 || objv.Length > 6 ) { throw new TclNumArgsException( interp, 2, objv, "string first last ?string?" ); } string string1 = objv[2].ToString(); int length1 = string1.Length - 1; int first = Util.getIntForIndex( interp, objv[3], length1 ); int last = Util.getIntForIndex( interp, objv[4], length1 ); if ( ( last < first ) || ( first > length1 ) || ( last < 0 ) ) { interp.setResult( objv[2] ); } else { if ( first < 0 ) { first = 0; } string start = string1.Substring( first ); int ind = ( ( last > length1 ) ? length1 : last ) - first + 1; string end; if ( ind <= 0 ) { end = start; } else if ( ind >= start.Length ) { end = ""; } else { end = start.Substring( ind ); } TclObject tstr = TclString.newInstance( string1.Substring( 0, ( first ) - ( 0 ) ) ); if ( objv.Length == 6 ) { TclString.append( tstr, objv[5] ); } if ( last < length1 ) { TclString.append( tstr, end ); } interp.setResult( tstr ); } break; } case STR_TOLOWER: case STR_TOUPPER: case STR_TOTITLE: { if ( objv.Length < 3 || objv.Length > 5 ) { throw new TclNumArgsException( interp, 2, objv, "string ?first? ?last?" ); } string string1 = objv[2].ToString(); if ( objv.Length == 3 ) { if ( index == STR_TOLOWER ) { interp.setResult( string1.ToLower() ); } else if ( index == STR_TOUPPER ) { interp.setResult( string1.ToUpper() ); } else { interp.setResult( Util.toTitle( string1 ) ); } } else { int length1 = string1.Length - 1; int first = Util.getIntForIndex( interp, objv[3], length1 ); if ( first < 0 ) { first = 0; } int last = first; if ( objv.Length == 5 ) { last = Util.getIntForIndex( interp, objv[4], length1 ); } if ( last >= length1 ) { last = length1; } if ( last < first ) { interp.setResult( objv[2] ); break; } string string2; StringBuilder buf = new StringBuilder(); buf.Append( string1.Substring( 0, ( first ) - ( 0 ) ) ); if ( last + 1 > length1 ) { string2 = string1.Substring( first ); } else { string2 = string1.Substring( first, ( last + 1 ) - ( first ) ); } if ( index == STR_TOLOWER ) { buf.Append( string2.ToLower() ); } else if ( index == STR_TOUPPER ) { buf.Append( string2.ToUpper() ); } else { buf.Append( Util.toTitle( string2 ) ); } if ( last + 1 <= length1 ) { buf.Append( string1.Substring( last + 1 ) ); } interp.setResult( buf.ToString() ); } break; } case STR_TRIM: { if ( objv.Length == 3 ) { // Case 1: "string trim str" -- // Remove leading and trailing white space interp.setResult( objv[2].ToString().Trim() ); } else if ( objv.Length == 4 ) { // Case 2: "string trim str chars" -- // Remove leading and trailing chars in the chars set string tmp = Util.TrimLeft( objv[2].ToString(), objv[3].ToString() ); interp.setResult( Util.TrimRight( tmp, objv[3].ToString() ) ); } else { // Case 3: Wrong # of args throw new TclNumArgsException( interp, 2, objv, "string ?chars?" ); } break; } case STR_TRIMLEFT: { if ( objv.Length == 3 ) { // Case 1: "string trimleft str" -- // Remove leading and trailing white space interp.setResult( Util.TrimLeft( objv[2].ToString() ) ); } else if ( objv.Length == 4 ) { // Case 2: "string trimleft str chars" -- // Remove leading and trailing chars in the chars set interp.setResult( Util.TrimLeft( objv[2].ToString(), objv[3].ToString() ) ); } else { // Case 3: Wrong # of args throw new TclNumArgsException( interp, 2, objv, "string ?chars?" ); } break; } case STR_TRIMRIGHT: { if ( objv.Length == 3 ) { // Case 1: "string trimright str" -- // Remove leading and trailing white space interp.setResult( Util.TrimRight( objv[2].ToString() ) ); } else if ( objv.Length == 4 ) { // Case 2: "string trimright str chars" -- // Remove leading and trailing chars in the chars set interp.setResult( Util.TrimRight( objv[2].ToString(), objv[3].ToString() ) ); } else { // Case 3: Wrong # of args throw new TclNumArgsException( interp, 2, objv, "string ?chars?" ); } break; } case STR_WORDEND: { if ( objv.Length != 4 ) { throw new TclNumArgsException( interp, 2, objv, "string index" ); } string string1 = objv[2].ToString(); char[] strArray = string1.ToCharArray(); int cur; int length1 = string1.Length; index = Util.getIntForIndex( interp, objv[3], length1 - 1 ); if ( index < 0 ) { index = 0; } if ( index >= length1 ) { interp.setResult( length1 ); return TCL.CompletionCode.RETURN; } for ( cur = index; cur < length1; cur++ ) { char c = strArray[cur]; if ( ( ( 1 << (int)System.Char.GetUnicodeCategory( c ) ) & WORD_BITS ) == 0 ) { break; } } if ( cur == index ) { cur = index + 1; } interp.setResult( cur ); break; } case STR_WORDSTART: { if ( objv.Length != 4 ) { throw new TclNumArgsException( interp, 2, objv, "string index" ); } string string1 = objv[2].ToString(); char[] strArray = string1.ToCharArray(); int cur; int length1 = string1.Length; index = Util.getIntForIndex( interp, objv[3], length1 - 1 ); if ( index > length1 ) { index = length1 - 1; } if ( index < 0 ) { interp.setResult( 0 ); return TCL.CompletionCode.RETURN; } for ( cur = index; cur >= 0; cur-- ) { char c = strArray[cur]; if ( ( ( 1 << (int)System.Char.GetUnicodeCategory( c ) ) & WORD_BITS ) == 0 ) { break; } } if ( cur != index ) { cur += 1; } interp.setResult( cur ); break; } } return TCL.CompletionCode.RETURN; } // return the number of Utf8 bytes that would be needed to store s private int Utf8Count( string s ) { int p = 0; int len = s.Length; char c; int sum = 0; while ( p < len ) { c = s[p++]; if ( ( c > 0 ) && ( c < 0x80 ) ) { sum += 1; continue; } if ( c <= 0x7FF ) { sum += 2; continue; } if ( c <= 0xFFFF ) { sum += 3; continue; } } return sum; } } // end StringCmd }