/* * LsearchCmd.java * * Copyright (c) 1997 Cornell University. * Copyright (c) 1997 Sun Microsystems, Inc. * Copyright (c) 1998-1999 by 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: LsearchCmd.java,v 1.2 2000/08/21 04:12:51 mo Exp $ * */ using System; namespace tcl.lang { /* * This class implements the built-in "lsearch" command in Tcl. */ class LsearchCmd : Command { private static readonly string[] options = new string[] { "-ascii", "-decreasing", "-dictionary", "-exact", "-increasing", "-integer", "-glob", "-real", "-regexp", "-sorted" }; internal const int LSEARCH_ASCII = 0; internal const int LSEARCH_DECREASING = 1; internal const int LSEARCH_DICTIONARY = 2; internal const int LSEARCH_EXACT = 3; internal const int LSEARCH_INCREASING = 4; internal const int LSEARCH_INTEGER = 5; internal const int LSEARCH_GLOB = 6; internal const int LSEARCH_REAL = 7; internal const int LSEARCH_REGEXP = 8; internal const int LSEARCH_SORTED = 9; internal const int ASCII = 0; internal const int DICTIONARY = 1; internal const int INTEGER = 2; internal const int REAL = 3; internal const int EXACT = 0; internal const int GLOB = 1; internal const int REGEXP = 2; internal const int SORTED = 3; /* *----------------------------------------------------------------------------- * * cmdProc -- * * This procedure is invoked to process the "lsearch" 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 ) { int mode = GLOB; int dataType = ASCII; bool isIncreasing = true; TclObject pattern; TclObject list; if ( objv.Length < 3 ) { throw new TclNumArgsException( interp, 1, objv, "?options? list pattern" ); } for ( int i = 1; i < objv.Length - 2; i++ ) { switch ( TclIndex.get( interp, objv[i], options, "option", 0 ) ) { case LSEARCH_ASCII: dataType = ASCII; break; case LSEARCH_DECREASING: isIncreasing = false; break; case LSEARCH_DICTIONARY: dataType = DICTIONARY; break; case LSEARCH_EXACT: mode = EXACT; break; case LSEARCH_INCREASING: isIncreasing = true; break; case LSEARCH_INTEGER: dataType = INTEGER; break; case LSEARCH_GLOB: mode = GLOB; break; case LSEARCH_REAL: dataType = REAL; break; case LSEARCH_REGEXP: mode = REGEXP; break; case LSEARCH_SORTED: mode = SORTED; break; } } // Make sure the list argument is a list object and get its length and // a pointer to its array of element pointers. TclObject[] listv = TclList.getElements( interp, objv[objv.Length - 2] ); TclObject patObj = objv[objv.Length - 1]; string patternBytes = null; int patInt = 0; double patDouble = 0.0; int length = 0; if ( mode == EXACT || mode == SORTED ) { switch ( dataType ) { case ASCII: case DICTIONARY: patternBytes = patObj.ToString(); length = patternBytes.Length; break; case INTEGER: patInt = TclInteger.get( interp, patObj ); break; case REAL: patDouble = TclDouble.get( interp, patObj ); break; } } else { patternBytes = patObj.ToString(); length = patternBytes.Length; } // Set default index value to -1, indicating failure; if we find the // item in the course of our search, index will be set to the correct // value. int index = -1; if ( mode == SORTED ) { // If the data is sorted, we can do a more intelligent search. int match = 0; int lower = -1; int upper = listv.Length; while ( lower + 1 != upper ) { int i = ( lower + upper ) / 2; switch ( dataType ) { case ASCII: { string bytes = listv[i].ToString(); match = patternBytes.CompareTo( bytes ); break; } case DICTIONARY: { string bytes = listv[i].ToString(); match = DictionaryCompare( patternBytes, bytes ); break; } case INTEGER: { int objInt = TclInteger.get( interp, listv[i] ); if ( patInt == objInt ) { match = 0; } else if ( patInt < objInt ) { match = -1; } else { match = 1; } break; } case REAL: { double objDouble = TclDouble.get( interp, listv[i] ); if ( patDouble == objDouble ) { match = 0; } else if ( patDouble < objDouble ) { match = -1; } else { match = 1; } break; } } if ( match == 0 ) { // Normally, binary search is written to stop when it // finds a match. If there are duplicates of an element in // the list, our first match might not be the first occurance. // Consider: 0 0 0 1 1 1 2 2 2 // To maintain consistancy with standard lsearch semantics, // we must find the leftmost occurance of the pattern in the // list. Thus we don't just stop searching here. This // variation means that a search always makes log n // comparisons (normal binary search might "get lucky" with // an early comparison). index = i; upper = i; } else if ( match > 0 ) { if ( isIncreasing ) { lower = i; } else { upper = i; } } else { if ( isIncreasing ) { upper = i; } else { lower = i; } } } } else { for ( int i = 0; i < listv.Length; i++ ) { bool match = false; switch ( mode ) { case SORTED: case EXACT: { switch ( dataType ) { case ASCII: { string bytes = listv[i].ToString(); int elemLen = bytes.Length; if ( length == elemLen ) { match = bytes.Equals( patternBytes ); } break; } case DICTIONARY: { string bytes = listv[i].ToString(); match = ( DictionaryCompare( bytes, patternBytes ) == 0 ); break; } case INTEGER: { int objInt = TclInteger.get( interp, listv[i] ); match = ( objInt == patInt ); break; } case REAL: { double objDouble = TclDouble.get( interp, listv[i] ); match = ( objDouble == patDouble ); break; } } break; } case GLOB: { match = Util.stringMatch( listv[i].ToString(), patternBytes ); break; } case REGEXP: { match = Util.regExpMatch( interp, listv[i].ToString(), patObj ); break; } } if ( match ) { index = i; break; } } } interp.setResult( index ); return TCL.CompletionCode.RETURN; } /* *---------------------------------------------------------------------- * * DictionaryCompare -> dictionaryCompare * * This function compares two strings as if they were being used in * an index or card catalog. The case of alphabetic characters is * ignored, except to break ties. Thus "B" comes before "b" but * after "a". Also, integers embedded in the strings compare in * numerical order. In other words, "x10y" comes after "x9y", not * before it as it would when using strcmp(). * * Results: * A negative result means that the first element comes before the * second, and a positive result means that the second element * should come first. A result of zero means the two elements * are equal and it doesn't matter which comes first. * * Side effects: * None. * *---------------------------------------------------------------------- */ private static int DictionaryCompare( string left, string right ) // The strings to compare { char[] leftArr = left.ToCharArray(); char[] rightArr = right.ToCharArray(); char leftChar, rightChar, leftLower, rightLower; int lInd = 0; int rInd = 0; int diff; int secondaryDiff = 0; while ( true ) { if ( ( rInd < rightArr.Length ) && ( System.Char.IsDigit( rightArr[rInd] ) ) && ( lInd < leftArr.Length ) && ( System.Char.IsDigit( leftArr[lInd] ) ) ) { // There are decimal numbers embedded in the two // strings. Compare them as numbers, rather than // strings. If one number has more leading zeros than // the other, the number with more leading zeros sorts // later, but only as a secondary choice. int zeros = 0; while ( ( rightArr[rInd] == '0' ) && ( rInd + 1 < rightArr.Length ) && ( System.Char.IsDigit( rightArr[rInd + 1] ) ) ) { rInd++; zeros--; } while ( ( leftArr[lInd] == '0' ) && ( lInd + 1 < leftArr.Length ) && ( System.Char.IsDigit( leftArr[lInd + 1] ) ) ) { lInd++; zeros++; } if ( secondaryDiff == 0 ) { secondaryDiff = zeros; } // The code below compares the numbers in the two // strings without ever converting them to integers. It // does this by first comparing the lengths of the // numbers and then comparing the digit values. diff = 0; while ( true ) { if ( ( diff == 0 ) && ( lInd < leftArr.Length ) && ( rInd < rightArr.Length ) ) { diff = leftArr[lInd] - rightArr[rInd]; } rInd++; lInd++; if ( rInd >= rightArr.Length || !System.Char.IsDigit( rightArr[rInd] ) ) { if ( lInd < leftArr.Length && System.Char.IsDigit( leftArr[lInd] ) ) { return 1; } else { // The two numbers have the same length. See // if their values are different. if ( diff != 0 ) { return diff; } break; } } else if ( lInd >= leftArr.Length || !System.Char.IsDigit( leftArr[lInd] ) ) { return -1; } } continue; } // Convert character to Unicode for comparison purposes. If either // string is at the terminating null, do a byte-wise comparison and // bail out immediately. if ( ( lInd < leftArr.Length ) && ( rInd < rightArr.Length ) ) { // Convert both chars to lower for the comparison, because // dictionary sorts are case insensitve. Covert to lower, not // upper, so chars between Z and a will sort before A (where most // other interesting punctuations occur) leftChar = leftArr[lInd++]; rightChar = rightArr[rInd++]; leftLower = System.Char.ToLower( leftChar ); rightLower = System.Char.ToLower( rightChar ); } else if ( lInd < leftArr.Length ) { diff = -rightArr[rInd]; break; } else if ( rInd < rightArr.Length ) { diff = leftArr[lInd]; break; } else { diff = 0; break; } diff = leftLower - rightLower; if ( diff != 0 ) { return diff; } else if ( secondaryDiff == 0 ) { if ( System.Char.IsUpper( leftChar ) && System.Char.IsLower( rightChar ) ) { secondaryDiff = -1; } else if ( System.Char.IsUpper( rightChar ) && System.Char.IsLower( leftChar ) ) { secondaryDiff = 1; } } } if ( diff == 0 ) { diff = secondaryDiff; } return diff; } } // end LsearchCmd }