wasCSharpSQLite – Rev 1

Subversion Repositories:
Rev:
/*
* 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
}