/*
* ScanCmd.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: ScanCmd.java,v 1.2 1999/05/09 01:22:09 dejong Exp $
*
*/
using System;
namespace tcl.lang
{
/// This class implements the built-in "scan" command in Tcl.
///
///
class ScanCmd : Command
{
/// This procedure is invoked to process the "scan" Tcl command.
/// See the user documentation for details on what it does.
///
/// Each iteration of the cmdProc compares the scanArr's current index to
/// the frmtArr's index. If the chars are equal then the indicies are
/// incremented. If a '%' is found in the frmtArr, the formatSpecifier
/// is parced from the frmtArr, the corresponding value is extracted from
/// the scanArr, and that value is set in the Tcl Interp.
///
/// If the chars are not equal, or the conversion fails, the boolean
/// scanArrDone is set to true, indicating the scanArr is not to be
/// parced and no new values are to be set. However the frmtArr is still
/// parced because of the priority of error messages. In the C version
/// of Tcl, bad format specifiers throw errors before incorrect argument
/// input or other scan errors. Thus we need to parce the entire frmtArr
/// to verify correct formating. This is dumb and inefficient but it is
/// consistent w/ the current C-version of Tcl.
///
public TCL.CompletionCode cmdProc( Interp interp, TclObject[] argv )
{
if ( argv.Length < 3 )
{
throw new TclNumArgsException( interp, 1, argv, "string format ?varName varName ...?" );
}
;
StrtoulResult strul; // Return value for parcing the scanArr when
// extracting integers/longs
StrtodResult strd;
; // Return value for parcing the scanArr when
// extracting doubles
char[] scanArr; // Array containing parce info
char[] frmtArr; // Array containing info on how to
// parse the scanArr
int scanIndex; // Index into the scan array
int frmtIndex; // Index into the frmt array
int tempIndex; // Temporary index holder
int argIndex; // Index into the current arg
int width; // Stores the user specified result width
int base_; // Base of the integer being converted
int numUnMatched; // Number of fields actually set.
int numMatched; // Number of fields actually matched.
int negateScan; // Mult by result, set to -1 if true
int i; // Generic variable
char ch; // Generic variable
bool cont; // Used in loops to indicate when to stop
bool scanOK; // Set to false if strtoul/strtod fails
bool scanArrDone; // Set to false if strtoul/strtod fails
bool widthFlag; // True is width is specified
bool discardFlag; // If a "%*" is in the formatString dont
// write output to arg
scanArr = argv[1].ToString().ToCharArray();
frmtArr = argv[2].ToString().ToCharArray();
width = base_ = numMatched = numUnMatched = 0;
scanIndex = frmtIndex = 0;
scanOK = true;
scanArrDone = false;
argIndex = 3;
// Skip all (if any) of the white space before getting to a char
frmtIndex = skipWhiteSpace( frmtArr, frmtIndex );
// Search through the frmtArr. If the next char is a '%' parse the
// next chars and determine the type (if any) of the format specifier.
// If the scanArr has been fully searched, do nothing but incerment
// "numUnMatched". The reason to continue the frmtArr search is for
// consistency in output. Previously scan format errors were reported
// before arg input mismatch, so this maintains the same level of error
// checking.
while ( frmtIndex < frmtArr.Length )
{
discardFlag = widthFlag = false;
negateScan = 1;
cont = true;
// Parce the format array and read in the correct value from the
// scan array. When the correct value is retrieved, set the
// variable (from argv) in the interp.
if ( frmtArr[frmtIndex] == '%' )
{
frmtIndex++;
checkOverFlow( interp, frmtArr, frmtIndex );
// Two '%'s in a row, do nothing...
if ( frmtArr[frmtIndex] == '%' )
{
frmtIndex++;
scanIndex++;
continue;
}
// Check for a discard field flag
if ( frmtArr[frmtIndex] == '*' )
{
discardFlag = true;
frmtIndex++;
checkOverFlow( interp, frmtArr, frmtIndex );
}
// Check for a width field and accept the 'h', 'l', 'L'
// characters, but do nothing with them.
//
// Note: The order of the width specifier and the other
// chars is unordered, so we need to iterate until all
// of the specifiers are identified.
while ( cont )
{
cont = false;
switch ( frmtArr[frmtIndex] )
{
case 'h':
case 'l':
case 'L':
{
// Just ignore these values
frmtIndex++;
cont = true;
break;
}
default:
{
if ( System.Char.IsDigit( frmtArr[frmtIndex] ) )
{
strul = Util.strtoul( new string( frmtArr ), frmtIndex, base_ );
frmtIndex = strul.index;
width = (int)strul.value;
widthFlag = true;
cont = true;
}
}
break;
}
checkOverFlow( interp, frmtArr, frmtIndex );
}
// On all conversion specifiers except 'c', move the
// scanIndex to the next non-whitespace.
ch = frmtArr[frmtIndex];
if ( ( ch != 'c' ) && ( ch != '[' ) && !scanArrDone )
{
scanIndex = skipWhiteSpace( scanArr, scanIndex );
}
if ( scanIndex >= scanArr.Length )
{
scanArrDone = true;
}
if ( ( scanIndex < scanArr.Length ) && ( ch != 'c' ) && ( ch != '[' ) )
{
// Since strtoul dosent take signed numbers, make the
// value positive and store the sign.
if ( scanArr[scanIndex] == '-' )
{
negateScan = -1;
scanIndex++;
width--;
}
else if ( scanArr[scanIndex] == '+' )
{
scanIndex++;
width--;
}
// The width+scanIndex might be greater than
// the scanArr so we need to re-adjust when this
// happens.
if ( widthFlag && ( width + scanIndex > scanArr.Length ) )
{
width = scanArr.Length - scanIndex;
}
}
if ( scanIndex >= scanArr.Length )
{
scanArrDone = true;
}
// Foreach iteration we want strul and strd to be
// null since we error check on this case.
strul = null;
strd = null;
switch ( ch )
{
case 'd':
case 'o':
case 'x':
{
if ( !scanArrDone )
{
if ( ch == 'd' )
{
base_ = 10;
}
else if ( ch == 'o' )
{
base_ = 8;
}
else
{
base_ = 16;
}
// If the widthFlag is set then convert only
// "width" characters to an ascii representation,
// else read in until the end of the integer. The
// scanIndex is moved to the point where we stop
// reading in.
if ( widthFlag )
{
strul = Util.strtoul( new string( scanArr, 0, width + scanIndex ), scanIndex, base_ );
}
else
{
strul = Util.strtoul( new string( scanArr ), scanIndex, base_ );
}
if ( strul.errno != 0 )
{
scanOK = false;
break;
}
scanIndex = strul.index;
if ( !discardFlag )
{
i = (int)strul.value * negateScan;
if ( argIndex == argv.Length )
numMatched--;
else
testAndSetVar( interp, argv, argIndex++, TclInteger.newInstance( i ) );
}
}
break;
}
case 'c':
{
if ( widthFlag )
{
errorCharFieldWidth( interp );
}
if ( !discardFlag && !scanArrDone )
{
testAndSetVar( interp, argv, argIndex++, TclInteger.newInstance( scanArr[scanIndex++] ) );
}
break;
}
case 's':
{
if ( !scanArrDone )
{
// If the widthFlag is set then read only "width"
// characters into the string, else read in until
// the first whitespace or endArr is found. The
// scanIndex is moved to the point where we stop
// reading in.
tempIndex = scanIndex;
if ( !widthFlag )
{
width = scanArr.Length;
}
for ( i = 0; ( scanIndex < scanArr.Length ) && ( i < width ); i++ )
{
ch = scanArr[scanIndex];
if ( ( ch == ' ' ) || ( ch == '\n' ) || ( ch == '\r' ) || ( ch == '\t' ) || ( ch == '\f' ) )
{
break;
}
scanIndex++;
}
if ( !discardFlag )
{
string str = new string( scanArr, tempIndex, scanIndex - tempIndex );
testAndSetVar( interp, argv, argIndex++, TclString.newInstance( str ) );
}
}
break;
}
case 'e':
case 'f':
case 'g':
{
if ( !scanArrDone )
{
// If the wisthFlag is set then read only "width"
// characters into the string, else read in until
// the first whitespace or endArr is found. The
// scanIndex is moved to the point where we stop
// reading in.
if ( widthFlag )
{
strd = Util.strtod( new string( scanArr, 0, width + scanIndex ), scanIndex );
}
else
{
strd = Util.strtod( new string( scanArr ), scanIndex );
}
if ( strd.errno != 0 )
{
scanOK = false;
break;
}
scanIndex = strd.index;
if ( !discardFlag )
{
double d = strd.value * negateScan;
testAndSetVar( interp, argv, argIndex++, TclDouble.newInstance( d ) );
}
}
break;
}
case '[':
{
bool charMatchFound = false;
bool charNotMatch = false;
char[] tempArr;
int startIndex;
int endIndex;
string unmatched = "unmatched [ in format string";
if ( ( ++frmtIndex ) >= frmtArr.Length )
{
throw new TclException( interp, unmatched );
}
if ( frmtArr[frmtIndex] == '^' )
{
charNotMatch = true;
frmtIndex += 2;
}
else
{
frmtIndex++;
}
tempIndex = frmtIndex - 1;
if ( frmtIndex >= frmtArr.Length )
{
throw new TclException( interp, unmatched );
}
// Extract the list of chars for matching.
while ( frmtArr[frmtIndex] != ']' )
{
if ( ( ++frmtIndex ) >= frmtArr.Length )
{
throw new TclException( interp, unmatched );
}
}
tempArr = new string( frmtArr, tempIndex, frmtIndex - tempIndex ).ToCharArray();
startIndex = scanIndex;
if ( charNotMatch )
{
// Format specifier contained a '^' so interate
// until one of the chars in tempArr is found.
while ( scanOK && !charMatchFound )
{
if ( scanIndex >= scanArr.Length )
{
scanOK = false;
break;
}
for ( i = 0; i < tempArr.Length; i++ )
{
if ( tempArr[i] == scanArr[scanIndex] )
{
charMatchFound = true;
break;
}
}
if ( widthFlag && ( ( scanIndex - startIndex ) >= width ) )
{
break;
}
if ( !charMatchFound )
{
scanIndex++;
}
}
}
else
{
// Iterate until the char in the scanArr is not
// in the tempArr.
charMatchFound = true;
while ( scanOK && charMatchFound )
{
if ( scanIndex >= scanArr.Length )
{
scanOK = false;
break;
}
charMatchFound = false;
for ( i = 0; i < tempArr.Length; i++ )
{
if ( tempArr[i] == scanArr[scanIndex] )
{
charMatchFound = true;
break;
}
}
if ( widthFlag && ( scanIndex - startIndex ) >= width )
{
break;
}
if ( charMatchFound )
{
scanIndex++;
}
}
}
// Indicates nothing was found.
endIndex = scanIndex - startIndex;
if ( endIndex <= 0 )
{
scanOK = false;
break;
}
if ( !discardFlag )
{
string str = new string( scanArr, startIndex, endIndex );
testAndSetVar( interp, argv, argIndex++, TclString.newInstance( str ) );
}
break;
}
default:
{
errorBadField( interp, ch );
}
break;
}
// As long as the scan was successful (scanOK), the format
// specifier did not contain a '*' (discardFlag), and
// we are not at the end of the scanArr (scanArrDone);
// increment the num of vars set in the interp. Otherwise
// increment the number of valid format specifiers.
if ( scanOK && !discardFlag && !scanArrDone )
{
numMatched++;
}
else if ( ( scanArrDone || !scanOK ) && !discardFlag )
{
numUnMatched++;
}
frmtIndex++;
}
else if ( scanIndex < scanArr.Length && scanArr[scanIndex] == frmtArr[frmtIndex] )
{
// No '%' was found, but the characters matched
scanIndex++;
frmtIndex++;
}
else
{
// No '%' found and the characters int frmtArr & scanArr
// did not match.
frmtIndex++;
}
}
// The numMatched is the return value: a count of the num of vars set.
// While the numUnMatched is the number of formatSpecifiers that
// passed the parsing stage, but did not match anything in the scanArr.
if ( ( numMatched + numUnMatched ) != ( argv.Length - 3 ) )
{
errorDiffVars( interp );
}
interp.setResult( TclInteger.newInstance( numMatched ) );
return TCL.CompletionCode.RETURN;
}
/// Given an array and an index into it, move the index forward
/// until a non-whitespace char is found.
///
///
/// - the array to search
///
/// - where to begin the search
///
/// The index value where the whitespace ends.
///
private int skipWhiteSpace( char[] arr, int index )
{
bool cont;
do
{
if ( index >= arr.Length )
{
return index;
}
cont = false;
switch ( arr[index] )
{
case '\t':
case '\n':
case '\r':
case '\f':
case ' ':
{
cont = true;
index++;
}
break;
}
}
while ( cont );
return index;
}
/// Called whenever the cmdProc wants to set an interp value.
/// This method
/// - verifies that there exisits a varName from the argv array,
///
- that the variable either dosent exisit or is of type scalar
///
- set the variable in interp if (1) and (2) are OK
///
///
///
/// - the Tcl interpreter
///
/// - the argument array
///
/// - the current index into the argv array
///
/// - the TclObject that the varName equals
///
///
private static void testAndSetVar( Interp interp, TclObject[] argv, int argIndex, TclObject tobj )
{
if ( argIndex < argv.Length )
{
try
{
interp.setVar( argv[argIndex].ToString(), tobj, 0 );
}
catch ( TclException e )
{
throw new TclException( interp, "couldn't set variable \"" + argv[argIndex].ToString() + "\"" );
}
}
else
{
errorDiffVars( interp );
}
}
/// Called whenever the frmtIndex in the cmdProc is changed. It verifies
/// the the array index is still within the bounds of the array. If no
/// throw error.
///
/// - The TclInterp which called the cmdProc method .
///
/// - The array to be checked.
///
/// - The new value for the array index.
///
private static void checkOverFlow( Interp interp, char[] arr, int index )
{
if ( ( index >= arr.Length ) || ( index < 0 ) )
{
throw new TclException( interp, "\"%n$\" argument index out of range" );
}
}
/// Called whenever the number of varName args do not match the number
/// of found and valid formatSpecifiers (matched and unmatched).
///
///
/// - The TclInterp which called the cmdProc method .
///
private static void errorDiffVars( Interp interp )
{
throw new TclException( interp, "different numbers of variable names and field specifiers" );
}
/// Called whenever the current char in the frmtArr is erroneous
///
///
/// - The TclInterp which called the cmdProc method .
///
/// - The erroneous character
///
private static void errorBadField( Interp interp, char fieldSpecifier )
{
throw new TclException( interp, "bad scan conversion character \"" + fieldSpecifier + "\"" );
}
/// Called whenever the a width field is used in a char ('c') format
/// specifier
///
///
/// - The TclInterp which called the cmdProc method .
///
private static void errorCharFieldWidth( Interp interp )
{
throw new TclException( interp, "field width may not be specified in %c conversion" );
}
}
}