/*
* ArrayCmd.java
*
* Copyright (c) 1997 Sun Microsystems, Inc.
*
* 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: ArrayCmd.java,v 1.4 2003/01/10 01:57:57 mdejong Exp $
*
*/
using System;
using System.Collections;
namespace tcl.lang
{
/// This class implements the built-in "array" command in Tcl.
class ArrayCmd : Command
{
internal static Type procClass = null;
private static readonly string[] validCmds = new string[] { "anymore", "donesearch", "exists", "get", "names", "nextelement", "set", "size", "startsearch", "unset" };
internal const int OPT_ANYMORE = 0;
internal const int OPT_DONESEARCH = 1;
internal const int OPT_EXISTS = 2;
internal const int OPT_GET = 3;
internal const int OPT_NAMES = 4;
internal const int OPT_NEXTELEMENT = 5;
internal const int OPT_SET = 6;
internal const int OPT_SIZE = 7;
internal const int OPT_STARTSEARCH = 8;
internal const int OPT_UNSET = 9;
/// This procedure is invoked to process the "array" Tcl command.
/// See the user documentation for details on what it does.
///
public TCL.CompletionCode cmdProc( Interp interp, TclObject[] objv )
{
Var var = null, array = null;
bool notArray = false;
string varName, msg;
int index;//, result;
if ( objv.Length < 3 )
{
throw new TclNumArgsException( interp, 1, objv, "option arrayName ?arg ...?" );
}
index = TclIndex.get( interp, objv[1], validCmds, "option", 0 );
// Locate the array variable (and it better be an array).
varName = objv[2].ToString();
Var[] retArray = Var.lookupVar( interp, varName, null, 0, null, false, false );
// Assign the values returned in the array
if ( retArray != null )
{
var = retArray[0];
array = retArray[1];
}
if ( ( var == null ) || !var.isVarArray() || var.isVarUndefined() )
{
notArray = true;
}
// Special array trace used to keep the env array in sync for
// array names, array get, etc.
if ( var != null && var.traces != null )
{
msg = Var.callTraces( interp, array, var, varName, null, ( TCL.VarFlag.LEAVE_ERR_MSG | TCL.VarFlag.NAMESPACE_ONLY | TCL.VarFlag.GLOBAL_ONLY | TCL.VarFlag.TRACE_ARRAY ) );
if ( (System.Object)msg != null )
{
throw new TclVarException( interp, varName, null, "trace array", msg );
}
}
switch ( index )
{
case OPT_ANYMORE:
{
if ( objv.Length != 4 )
{
throw new TclNumArgsException( interp, 2, objv, "arrayName searchId" );
}
if ( notArray )
{
errorNotArray( interp, objv[2].ToString() );
}
if ( var.sidVec == null )
{
errorIllegalSearchId( interp, objv[2].ToString(), objv[3].ToString() );
}
SearchId e = var.getSearch( objv[3].ToString() );
if ( e == null )
{
errorIllegalSearchId( interp, objv[2].ToString(), objv[3].ToString() );
}
if ( e.HasMore )
{
interp.setResult( "1" );
}
else
{
interp.setResult( "0" );
}
break;
}
case OPT_DONESEARCH:
{
if ( objv.Length != 4 )
{
throw new TclNumArgsException( interp, 2, objv, "arrayName searchId" );
}
if ( notArray )
{
errorNotArray( interp, objv[2].ToString() );
}
bool rmOK = true;
if ( var.sidVec != null )
{
rmOK = ( var.removeSearch( objv[3].ToString() ) );
}
if ( ( var.sidVec == null ) || !rmOK )
{
errorIllegalSearchId( interp, objv[2].ToString(), objv[3].ToString() );
}
break;
}
case OPT_EXISTS:
{
if ( objv.Length != 3 )
{
throw new TclNumArgsException( interp, 2, objv, "arrayName" );
}
interp.setResult( !notArray );
break;
}
case OPT_GET:
{
// Due to the differences in the hashtable implementation
// from the Tcl core and Java, the output will be rearranged.
// This is not a negative side effect, however, test results
// will differ.
if ( ( objv.Length != 3 ) && ( objv.Length != 4 ) )
{
throw new TclNumArgsException( interp, 2, objv, "arrayName ?pattern?" );
}
if ( notArray )
{
return TCL.CompletionCode.RETURN;
}
string pattern = null;
if ( objv.Length == 4 )
{
pattern = objv[3].ToString();
}
Hashtable table = (Hashtable)var.value;
TclObject tobj = TclList.newInstance();
string arrayName = objv[2].ToString();
string key, strValue;
Var var2;
// Go through each key in the hash table. If there is a
// pattern, test for a match. Each valid key and its value
// is written into sbuf, which is returned.
// FIXME : do we need to port over the 8.1 code for this loop?
for ( IDictionaryEnumerator e = table.GetEnumerator(); e.MoveNext(); )
{
key = ( (string)e.Key );
var2 = (Var)e.Value;
if ( var2.isVarUndefined() )
{
continue;
}
if ( (System.Object)pattern != null && !Util.stringMatch( key, pattern ) )
{
continue;
}
strValue = interp.getVar( arrayName, key, 0 ).ToString();
TclList.append( interp, tobj, TclString.newInstance( key ) );
TclList.append( interp, tobj, TclString.newInstance( strValue ) );
}
interp.setResult( tobj );
break;
}
case OPT_NAMES:
{
if ( ( objv.Length != 3 ) && ( objv.Length != 4 ) )
{
throw new TclNumArgsException( interp, 2, objv, "arrayName ?pattern?" );
}
if ( notArray )
{
return TCL.CompletionCode.RETURN;
}
string pattern = null;
if ( objv.Length == 4 )
{
pattern = objv[3].ToString();
}
Hashtable table = (Hashtable)var.value;
TclObject tobj = TclList.newInstance();
string key;
// Go through each key in the hash table. If there is a
// pattern, test for a match. Each valid key and its value
// is written into sbuf, which is returned.
for ( IDictionaryEnumerator e = table.GetEnumerator(); e.MoveNext(); )
{
key = (string)e.Key;
Var elem = (Var)e.Value;
if ( !elem.isVarUndefined() )
{
if ( (System.Object)pattern != null )
{
if ( !Util.stringMatch( key, pattern ) )
{
continue;
}
}
TclList.append( interp, tobj, TclString.newInstance( key ) );
}
}
interp.setResult( tobj );
break;
}
case OPT_NEXTELEMENT:
{
if ( objv.Length != 4 )
{
throw new TclNumArgsException( interp, 2, objv, "arrayName searchId" );
}
if ( notArray )
{
errorNotArray( interp, objv[2].ToString() );
}
if ( var.sidVec == null )
{
errorIllegalSearchId( interp, objv[2].ToString(), objv[3].ToString() );
}
SearchId e = var.getSearch( objv[3].ToString() );
if ( e == null )
{
errorIllegalSearchId( interp, objv[2].ToString(), objv[3].ToString() );
}
if ( e.HasMore )
{
Hashtable table = (Hashtable)var.value;
DictionaryEntry entry = e.nextEntry();
string key = (string)entry.Key;
Var elem = (Var)entry.Value;
if ( ( elem.flags & VarFlags.UNDEFINED ) == 0 )
{
interp.setResult( key );
}
else
{
interp.setResult( "" );
}
}
break;
}
case OPT_SET:
{
if ( objv.Length != 4 )
{
throw new TclNumArgsException( interp, 2, objv, "arrayName list" );
}
int size = TclList.getLength( interp, objv[3] );
if ( size % 2 != 0 )
{
throw new TclException( interp, "list must have an even number of elements" );
}
int i;
string name1 = objv[2].ToString();
string name2, strValue;
// Set each of the array variable names in the interp
for ( i = 0; i < size; i++ )
{
name2 = TclList.index( interp, objv[3], i++ ).ToString();
strValue = TclList.index( interp, objv[3], i ).ToString();
interp.setVar( name1, name2, TclString.newInstance( strValue ), 0 );
}
break;
}
case OPT_SIZE:
{
if ( objv.Length != 3 )
{
throw new TclNumArgsException( interp, 2, objv, "arrayName" );
}
if ( notArray )
{
interp.setResult( 0 );
}
else
{
Hashtable table = (Hashtable)var.value;
int size = 0;
for ( IDictionaryEnumerator e = table.GetEnumerator(); e.MoveNext(); )
{
Var elem = (Var)e.Value;
if ( ( elem.flags & VarFlags.UNDEFINED ) == 0 )
{
size++;
}
}
interp.setResult( size );
}
break;
}
case OPT_STARTSEARCH:
{
if ( objv.Length != 3 )
{
throw new TclNumArgsException( interp, 2, objv, "arrayName" );
}
if ( notArray )
{
errorNotArray( interp, objv[2].ToString() );
}
if ( var.sidVec == null )
{
var.sidVec = new ArrayList( 10 );
}
// Create a SearchId Object:
// To create a new SearchId object, a unique string
// identifier needs to be composed and we need to
// create an Enumeration of the array keys. The
// unique string identifier is created from three
// strings:
//
// "s-" is the default prefix
// "i" is a unique number that is 1+ the greatest
// SearchId index currently on the ArrayVar.
// "name" is the name of the array
//
// Once the SearchId string is created we construct a
// new SearchId object using the string and the
// Enumeration. From now on the string is used to
// uniquely identify the SearchId object.
int i = var.NextIndex;
string s = "s-" + i + "-" + objv[2].ToString();
IDictionaryEnumerator e = ( (Hashtable)var.value ).GetEnumerator();
var.sidVec.Add( new SearchId( e, s, i ) );
interp.setResult( s );
break;
}
case OPT_UNSET:
{
string pattern;
string name;
if ( ( objv.Length != 3 ) && ( objv.Length != 4 ) )
{
throw new TclNumArgsException( interp, 2, objv, "arrayName ?pattern?" );
}
if ( notArray )
{
//Ignot this error -- errorNotArray(interp, objv[2].ToString());
break;
}
if ( objv.Length == 3 )
{
// When no pattern is given, just unset the whole array
interp.unsetVar( objv[2], 0 );
}
else
{
pattern = objv[3].ToString();
Hashtable table = (Hashtable)( ( (Hashtable)var.value ).Clone() );
for ( IDictionaryEnumerator e = table.GetEnumerator(); e.MoveNext(); )
{
name = (string)e.Key;
Var elem = (Var)e.Value;
if ( var.isVarUndefined() )
{
continue;
}
if ( Util.stringMatch( name, pattern ) )
{
interp.unsetVar( varName, name, 0 );
}
}
}
break;
}
}
return TCL.CompletionCode.RETURN;
}
/// Error meassage thrown when an invalid identifier is used
/// to access an array.
///
///
/// currrent interpreter.
///
/// var is the string representation of the
/// variable that was passed in.
///
private static void errorNotArray( Interp interp, string var )
{
throw new TclException( interp, "\"" + var + "\" isn't an array" );
}
/// Error message thrown when an invalid SearchId is used. The
/// string used to reference the SearchId is parced to determine
/// the reason for the failure.
///
///
/// currrent interpreter.
///
/// sid is the string represenation of the
/// SearchId that was passed in.
///
internal static void errorIllegalSearchId( Interp interp, string varName, string sid )
{
int val = validSearchId( sid.ToCharArray(), varName );
if ( val == 1 )
{
throw new TclException( interp, "couldn't find search \"" + sid + "\"" );
}
else if ( val == 0 )
{
throw new TclException( interp, "illegal search identifier \"" + sid + "\"" );
}
else
{
throw new TclException( interp, "search identifier \"" + sid + "\" isn't for variable \"" + varName + "\"" );
}
}
/// A valid SearchId is represented by the format s-#-arrayName. If
/// the SearchId string does not match this format than it is illegal,
/// else we cannot find it. This method is used by the
/// ErrorIllegalSearchId method to determine the type of error message.
///
///
/// pattern[] is the string use dto identify the SearchId
///
/// 1 if its a valid searchID; 0 if it is not a valid searchId,
/// but it is for the array, -1 if it is not a valid searchId and NOT
/// for the array.
///
private static int validSearchId( char[] pattern, string varName )
{
int i;
if ( ( pattern[0] != 's' ) || ( pattern[1] != '-' ) || ( pattern[2] < '0' ) || ( pattern[2] > '9' ) )
{
return 0;
}
for ( i = 3; ( i < pattern.Length && pattern[i] != '-' ); i++ )
{
if ( pattern[i] < '0' || pattern[i] > '9' )
{
return 0;
}
}
if ( ++i >= pattern.Length )
{
return 0;
}
if ( varName.Equals( new string( pattern, i, ( pattern.Length - i ) ) ) )
{
return 1;
}
else
{
return -1;
}
}
}
}