/*
* 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
}