/TCL/src/commands/LsearchCmd.cs
C# | 510 lines | 356 code | 60 blank | 94 comment | 83 complexity | 8b17d7176c0f48b1918dff111b91b448 MD5 | raw file
- /*
- * 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
- }