/* * BinaryCmd.java -- * * Implements the built-in "binary" Tcl command. * * Copyright (c) 1999 Christian Krone. * Copyright (c) 1997 by 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: BinaryCmd.java,v 1.2 2002/05/07 06:58:06 mdejong Exp $ * */ using System; using System.Text; using System.IO; namespace tcl.lang { /* * This class implements the built-in "binary" command in Tcl. */ class BinaryCmd : Command { private static readonly string[] validCmds = new string[] { "format", "scan" }; private const string HEXDIGITS = "0123456789abcdef"; private const int CMD_FORMAT = 0; private const int CMD_SCAN = 1; // The following constants are used by GetFormatSpec to indicate various // special conditions in the parsing of a format specifier. // Use all elements in the argument. private const int BINARY_ALL = -1; // No count was specified in format. private const int BINARY_NOCOUNT = -2; // End of format was found. private const char FORMAT_END = ' '; public TCL.CompletionCode cmdProc( Interp interp, TclObject[] argv ) { int arg; // Index of next argument to consume. char[] format = null; // User specified format string. char cmd; // Current format character. int cursor; // Current position within result buffer. int maxPos; // Greatest position within result buffer that // cursor has visited. int value = 0; // Current integer value to be packed. // Initialized to avoid compiler warning. int offset, size = 0, length;//, index; if ( argv.Length < 2 ) { throw new TclNumArgsException( interp, 1, argv, "option ?arg arg ...?" ); } int cmdIndex = TclIndex.get( interp, argv[1], validCmds, "option", 0 ); switch ( cmdIndex ) { case CMD_FORMAT: { if ( argv.Length < 3 ) { throw new TclNumArgsException( interp, 2, argv, "formatString ?arg arg ...?" ); } // To avoid copying the data, we format the string in two passes. // The first pass computes the size of the output buffer. The // second pass places the formatted data into the buffer. format = argv[2].ToString().ToCharArray(); arg = 3; length = 0; offset = 0; System.Int32 parsePos = 0; while ( ( cmd = GetFormatSpec( format, ref parsePos ) ) != FORMAT_END ) { int count = GetFormatCount( format, ref parsePos ); switch ( cmd ) { case 'a': case 'A': case 'b': case 'B': case 'h': case 'H': { // For string-type specifiers, the count corresponds // to the number of bytes in a single argument. if ( arg >= argv.Length ) { missingArg( interp ); } if ( count == BINARY_ALL ) { count = TclByteArray.getLength( interp, argv[arg] ); } else if ( count == BINARY_NOCOUNT ) { count = 1; } arg++; switch ( cmd ) { case 'a': case 'A': offset += count; break; case 'b': case 'B': offset += ( count + 7 ) / 8; break; case 'h': case 'H': offset += ( count + 1 ) / 2; break; } break; } case 'c': case 's': case 'S': case 'i': case 'I': case 'f': case 'd': { if ( arg >= argv.Length ) { missingArg( interp ); } switch ( cmd ) { case 'c': size = 1; break; case 's': case 'S': size = 2; break; case 'i': case 'I': size = 4; break; case 'f': size = 4; break; case 'd': size = 8; break; } // For number-type specifiers, the count corresponds // to the number of elements in the list stored in // a single argument. If no count is specified, then // the argument is taken as a single non-list value. if ( count == BINARY_NOCOUNT ) { arg++; count = 1; } else { int listc = TclList.getLength( interp, argv[arg++] ); if ( count == BINARY_ALL ) { count = listc; } else if ( count > listc ) { throw new TclException( interp, "number of elements in list" + " does not match count" ); } } offset += count * size; break; } case 'x': { if ( count == BINARY_ALL ) { throw new TclException( interp, "cannot use \"*\"" + " in format string with \"x\"" ); } if ( count == BINARY_NOCOUNT ) { count = 1; } offset += count; break; } case 'X': { if ( count == BINARY_NOCOUNT ) { count = 1; } if ( ( count > offset ) || ( count == BINARY_ALL ) ) { count = offset; } if ( offset > length ) { length = offset; } offset -= count; break; } case '@': { if ( offset > length ) { length = offset; } if ( count == BINARY_ALL ) { offset = length; } else if ( count == BINARY_NOCOUNT ) { alephWithoutCount( interp ); } else { offset = count; } break; } default: { badField( interp, cmd ); } break; } } if ( offset > length ) { length = offset; } if ( length == 0 ) { return TCL.CompletionCode.RETURN; } // Prepare the result object by preallocating the calculated // number of bytes and filling with nulls. TclObject resultObj = TclByteArray.newInstance(); byte[] resultBytes = TclByteArray.setLength( interp, resultObj, length ); interp.setResult( resultObj ); // Pack the data into the result object. Note that we can skip // the error checking during this pass, since we have already // parsed the string once. arg = 3; cursor = 0; maxPos = cursor; parsePos = 0; while ( ( cmd = GetFormatSpec( format, ref parsePos ) ) != FORMAT_END ) { int count = GetFormatCount( format, ref parsePos ); if ( ( count == 0 ) && ( cmd != '@' ) ) { arg++; continue; } switch ( cmd ) { case 'a': case 'A': { byte pad = ( cmd == 'a' ) ? (byte)0 : (byte)SupportClass.Identity( ' ' ); byte[] bytes = TclByteArray.getBytes( interp, argv[arg++] ); length = bytes.Length; if ( count == BINARY_ALL ) { count = length; } else if ( count == BINARY_NOCOUNT ) { count = 1; } if ( length >= count ) { Array.Copy( bytes, 0, resultBytes, cursor, count ); } else { Array.Copy( bytes, 0, resultBytes, cursor, length ); for ( int ix = 0; ix < count - length; ix++ ) { resultBytes[cursor + length + ix] = pad; } } cursor += count; break; } case 'b': case 'B': { char[] str = argv[arg++].ToString().ToCharArray(); if ( count == BINARY_ALL ) { count = str.Length; } else if ( count == BINARY_NOCOUNT ) { count = 1; } int last = cursor + ( ( count + 7 ) / 8 ); if ( count > str.Length ) { count = str.Length; } if ( cmd == 'B' ) { for ( offset = 0; offset < count; offset++ ) { value <<= 1; if ( str[offset] == '1' ) { value |= 1; } else if ( str[offset] != '0' ) { expectedButGot( interp, "binary", new string( str ) ); } if ( ( ( offset + 1 ) % 8 ) == 0 ) { resultBytes[cursor++] = (byte)value; value = 0; } } } else { for ( offset = 0; offset < count; offset++ ) { value >>= 1; if ( str[offset] == '1' ) { value |= 128; } else if ( str[offset] != '0' ) { expectedButGot( interp, "binary", new string( str ) ); } if ( ( ( offset + 1 ) % 8 ) == 0 ) { resultBytes[cursor++] = (byte)value; value = 0; } } } if ( ( offset % 8 ) != 0 ) { if ( cmd == 'B' ) { value <<= 8 - ( offset % 8 ); } else { value >>= 8 - ( offset % 8 ); } resultBytes[cursor++] = (byte)value; } while ( cursor < last ) { resultBytes[cursor++] = 0; } break; } case 'h': case 'H': { char[] str = argv[arg++].ToString().ToCharArray(); if ( count == BINARY_ALL ) { count = str.Length; } else if ( count == BINARY_NOCOUNT ) { count = 1; } int last = cursor + ( ( count + 1 ) / 2 ); if ( count > str.Length ) { count = str.Length; } if ( cmd == 'H' ) { for ( offset = 0; offset < count; offset++ ) { value <<= 4; int c = HEXDIGITS.IndexOf( Char.ToLower( str[offset] ) ); if ( c < 0 ) { expectedButGot( interp, "hexadecimal", new string( str ) ); } value |= ( c & 0xf ); if ( ( offset % 2 ) != 0 ) { resultBytes[cursor++] = (byte)value; value = 0; } } } else { for ( offset = 0; offset < count; offset++ ) { value >>= 4; int c = HEXDIGITS.IndexOf( Char.ToLower( str[offset] ) ); if ( c < 0 ) { expectedButGot( interp, "hexadecimal", new string( str ) ); } value |= ( ( c << 4 ) & 0xf0 ); if ( ( offset % 2 ) != 0 ) { resultBytes[cursor++] = (byte)value; value = 0; } } } if ( ( offset % 2 ) != 0 ) { if ( cmd == 'H' ) { value <<= 4; } else { value >>= 4; } resultBytes[cursor++] = (byte)value; } while ( cursor < last ) { resultBytes[cursor++] = 0; } break; } case 'c': case 's': case 'S': case 'i': case 'I': case 'f': case 'd': { TclObject[] listv; if ( count == BINARY_NOCOUNT ) { listv = new TclObject[1]; listv[0] = argv[arg++]; count = 1; } else { listv = TclList.getElements( interp, argv[arg++] ); if ( count == BINARY_ALL ) { count = listv.Length; } } for ( int ix = 0; ix < count; ix++ ) { cursor = FormatNumber( interp, cmd, listv[ix], resultBytes, cursor ); } break; } case 'x': { if ( count == BINARY_NOCOUNT ) { count = 1; } for ( int ix = 0; ix < count; ix++ ) { resultBytes[cursor++] = 0; } break; } case 'X': { if ( cursor > maxPos ) { maxPos = cursor; } if ( count == BINARY_NOCOUNT ) { count = 1; } if ( count == BINARY_ALL || count > cursor ) { cursor = 0; } else { cursor -= count; } break; } case '@': { if ( cursor > maxPos ) { maxPos = cursor; } if ( count == BINARY_ALL ) { cursor = maxPos; } else { cursor = count; } break; } } } break; } case CMD_SCAN: { if ( argv.Length < 4 ) { throw new TclNumArgsException( interp, 2, argv, "value formatString ?varName varName ...?" ); } byte[] src = TclByteArray.getBytes( interp, argv[2] ); length = src.Length; format = argv[3].ToString().ToCharArray(); arg = 4; cursor = 0; offset = 0; System.Int32 parsePos = 0; while ( ( cmd = GetFormatSpec( format, ref parsePos ) ) != FORMAT_END ) { int count = GetFormatCount( format, ref parsePos ); switch ( cmd ) { case 'a': case 'A': { if ( arg >= argv.Length ) { missingArg( interp ); } if ( count == BINARY_ALL ) { count = length - offset; } else { if ( count == BINARY_NOCOUNT ) { count = 1; } if ( count > length - offset ) { break; } } size = count; // Trim trailing nulls and spaces, if necessary. if ( cmd == 'A' ) { while ( size > 0 ) { if ( src[offset + size - 1] != '\x0000' && src[offset + size - 1] != ' ' ) { break; } size--; } } interp.setVar( argv[arg++], TclByteArray.newInstance( src, offset, size ), 0 ); offset += count; break; } case 'b': case 'B': { if ( arg >= argv.Length ) { missingArg( interp ); } if ( count == BINARY_ALL ) { count = ( length - offset ) * 8; } else { if ( count == BINARY_NOCOUNT ) { count = 1; } if ( count > ( length - offset ) * 8 ) { break; } } StringBuilder s = new StringBuilder( count ); int thisOffset = offset; if ( cmd == 'b' ) { for ( int ix = 0; ix < count; ix++ ) { if ( ( ix % 8 ) != 0 ) { value >>= 1; } else { value = src[thisOffset++]; } s.Append( ( value & 1 ) != 0 ? '1' : '0' ); } } else { for ( int ix = 0; ix < count; ix++ ) { if ( ( ix % 8 ) != 0 ) { value <<= 1; } else { value = src[thisOffset++]; } s.Append( ( value & 0x80 ) != 0 ? '1' : '0' ); } } interp.setVar( argv[arg++], TclString.newInstance( s.ToString() ), 0 ); offset += ( count + 7 ) / 8; break; } case 'h': case 'H': { if ( arg >= argv.Length ) { missingArg( interp ); } if ( count == BINARY_ALL ) { count = ( length - offset ) * 2; } else { if ( count == BINARY_NOCOUNT ) { count = 1; } if ( count > ( length - offset ) * 2 ) { break; } } StringBuilder s = new StringBuilder( count ); int thisOffset = offset; if ( cmd == 'h' ) { for ( int ix = 0; ix < count; ix++ ) { if ( ( ix % 2 ) != 0 ) { value >>= 4; } else { value = src[thisOffset++]; } s.Append( HEXDIGITS[value & 0xf] ); } } else { for ( int ix = 0; ix < count; ix++ ) { if ( ( ix % 2 ) != 0 ) { value <<= 4; } else { value = src[thisOffset++]; } s.Append( HEXDIGITS[value >> 4 & 0xf] ); } } interp.setVar( argv[arg++], TclString.newInstance( s.ToString() ), 0 ); offset += ( count + 1 ) / 2; break; } case 'c': case 's': case 'S': case 'i': case 'I': case 'f': case 'd': { if ( arg >= argv.Length ) { missingArg( interp ); } switch ( cmd ) { case 'c': size = 1; break; case 's': case 'S': size = 2; break; case 'i': case 'I': size = 4; break; case 'f': size = 4; break; case 'd': size = 8; break; } TclObject valueObj; if ( count == BINARY_NOCOUNT ) { if ( length - offset < size ) { break; } valueObj = ScanNumber( src, offset, cmd ); offset += size; } else { if ( count == BINARY_ALL ) { count = ( length - offset ) / size; } if ( length - offset < count * size ) { break; } valueObj = TclList.newInstance(); int thisOffset = offset; for ( int ix = 0; ix < count; ix++ ) { TclList.append( null, valueObj, ScanNumber( src, thisOffset, cmd ) ); thisOffset += size; } offset += count * size; } interp.setVar( argv[arg++], valueObj, 0 ); break; } case 'x': { if ( count == BINARY_NOCOUNT ) { count = 1; } if ( count == BINARY_ALL || count > length - offset ) { offset = length; } else { offset += count; } break; } case 'X': { if ( count == BINARY_NOCOUNT ) { count = 1; } if ( count == BINARY_ALL || count > offset ) { offset = 0; } else { offset -= count; } break; } case '@': { if ( count == BINARY_NOCOUNT ) { alephWithoutCount( interp ); } if ( count == BINARY_ALL || count > length ) { offset = length; } else { offset = count; } break; } default: { badField( interp, cmd ); } break; } } // Set the result to the last position of the cursor. interp.setResult( arg - 4 ); } break; } return TCL.CompletionCode.RETURN; } private char GetFormatSpec( char[] format, ref System.Int32 parsePos ) // Current position in input. { int ix = parsePos; // Skip any leading blanks. while ( ix < format.Length && format[ix] == ' ' ) { ix++; } // The string was empty, except for whitespace, so fail. if ( ix >= format.Length ) { parsePos = ix; return FORMAT_END; } // Extract the command character. parsePos = ix + 1; return format[ix++]; } private int GetFormatCount( char[] format, ref System.Int32 parsePos ) // Current position in input. { int ix = parsePos; // Extract any trailing digits or '*'. if ( ix < format.Length && format[ix] == '*' ) { parsePos = ix + 1; return BINARY_ALL; } else if ( ix < format.Length && System.Char.IsDigit( format[ix] ) ) { int length = 1; while ( ix + length < format.Length && System.Char.IsDigit( format[ix + length] ) ) { length++; } parsePos = ix + length; return System.Int32.Parse( new string( format, ix, length ) ); } else { return BINARY_NOCOUNT; } } internal static int FormatNumber( Interp interp, char type, TclObject src, byte[] resultBytes, int cursor ) { if ( type == 'd' ) { double dvalue = TclDouble.get( interp, src ); MemoryStream ms = new MemoryStream( resultBytes, cursor, 8 ); BinaryWriter writer = new BinaryWriter( ms ); writer.Write( dvalue ); cursor += 8; writer.Close(); ms.Close(); } else if ( type == 'f' ) { float fvalue = (float)TclDouble.get( interp, src ); MemoryStream ms = new MemoryStream( resultBytes, cursor, 4 ); BinaryWriter writer = new BinaryWriter( ms ); writer.Write( fvalue ); cursor += 4; writer.Close(); ms.Close(); } else { int value = TclInteger.get( interp, src ); if ( type == 'c' ) { resultBytes[cursor++] = (byte)value; } else if ( type == 's' ) { resultBytes[cursor++] = (byte)value; resultBytes[cursor++] = (byte)( value >> 8 ); } else if ( type == 'S' ) { resultBytes[cursor++] = (byte)( value >> 8 ); resultBytes[cursor++] = (byte)value; } else if ( type == 'i' ) { resultBytes[cursor++] = (byte)value; resultBytes[cursor++] = (byte)( value >> 8 ); resultBytes[cursor++] = (byte)( value >> 16 ); resultBytes[cursor++] = (byte)( value >> 24 ); } else if ( type == 'I' ) { resultBytes[cursor++] = (byte)( value >> 24 ); resultBytes[cursor++] = (byte)( value >> 16 ); resultBytes[cursor++] = (byte)( value >> 8 ); resultBytes[cursor++] = (byte)value; } } return cursor; } private static TclObject ScanNumber( byte[] src, int pos, int type ) // Format character from "binary scan" { switch ( type ) { case 'c': { return TclInteger.newInstance( (sbyte)src[pos] ); } case 's': { short value = (short)( ( src[pos] & 0xff ) + ( ( src[pos + 1] & 0xff ) << 8 ) ); return TclInteger.newInstance( (int)value ); } case 'S': { short value = (short)( ( src[pos + 1] & 0xff ) + ( ( src[pos] & 0xff ) << 8 ) ); return TclInteger.newInstance( (int)value ); } case 'i': { int value = ( src[pos] & 0xff ) + ( ( src[pos + 1] & 0xff ) << 8 ) + ( ( src[pos + 2] & 0xff ) << 16 ) + ( ( src[pos + 3] & 0xff ) << 24 ); return TclInteger.newInstance( value ); } case 'I': { int value = ( src[pos + 3] & 0xff ) + ( ( src[pos + 2] & 0xff ) << 8 ) + ( ( src[pos + 1] & 0xff ) << 16 ) + ( ( src[pos] & 0xff ) << 24 ); return TclInteger.newInstance( value ); } case 'f': { MemoryStream ms = new MemoryStream( src, pos, 4, false ); BinaryReader reader = new BinaryReader( ms ); double fvalue = reader.ReadSingle(); reader.Close(); ms.Close(); return TclDouble.newInstance( fvalue ); } case 'd': { MemoryStream ms = new MemoryStream( src, pos, 8, false ); BinaryReader reader = new BinaryReader( ms ); double dvalue = reader.ReadDouble(); reader.Close(); ms.Close(); return TclDouble.newInstance( dvalue ); } } return null; } /// Called whenever a format specifier was detected /// but there are not enough arguments specified. /// /// /// - The TclInterp which called the cmdProc method. /// private static void missingArg( Interp interp ) { throw new TclException( interp, "not enough arguments for all format specifiers" ); } /// Called whenever an invalid format specifier was detected. /// /// /// - The TclInterp which called the cmdProc method. /// /// - The invalid field specifier. /// private static void badField( Interp interp, char cmd ) { throw new TclException( interp, "bad field specifier \"" + cmd + "\"" ); } /// Called whenever a letter aleph character (@) was detected /// but there was no count specified. /// /// /// - The TclInterp which called the cmdProc method. /// private static void alephWithoutCount( Interp interp ) { throw new TclException( interp, "missing count for \"@\" field specifier" ); } /// Called whenever a format was found which restricts the valid range /// of characters in the specified string, but the string contains /// at least one char not in this range. /// /// /// - The TclInterp which called the cmdProc method. /// private static void expectedButGot( Interp interp, string expected, string str ) { throw new TclException( interp, "expected " + expected + " string but got \"" + str + "\" instead" ); } } // end BinaryCmd }