wasCSharpSQLite – Rev 1

Subversion Repositories:
Rev:
/* 
* PackageCmd.java --
*
*       This class implements the built-in "package" command in Tcl.
*
* 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: PackageCmd.java,v 1.4 2002/04/12 21:00:26 mdejong Exp $
*/
using System.Collections;
using System.Text;

namespace tcl.lang
{

  class PackageCmd : Command
  {

    private static readonly string[] validCmds = new string[] { "forget", "ifneeded", "names", "present", "provide", "require", "unknown", "vcompare", "versions", "vsatisfies" };

    private const int OPT_FORGET = 0;
    private const int OPT_IFNEEDED = 1;
    private const int OPT_NAMES = 2;
    private const int OPT_PRESENT = 3;
    private const int OPT_PROVIDE = 4;
    private const int OPT_REQUIRE = 5;
    private const int OPT_UNKNOWN = 6;
    private const int OPT_VCOMPARE = 7;
    private const int OPT_VERSIONS = 8;
    private const int OPT_VSATISFIES = 9;
    internal static void pkgProvide( Interp interp, string pkgName, string version )
    {
      Package pkg;

      // Validate the version string that was passed in.

      checkVersion( interp, version );
      pkg = findPackage( interp, pkgName );
      if ( (System.Object)pkg.version == null )
      {
        pkg.version = version;
        return;
      }
      if ( compareVersions( pkg.version, version, null ) != 0 )
      {
        throw new TclException( interp, "conflicting versions provided for package \"" + pkgName + "\": " + pkg.version + ", then " + version );
      }
    }
    internal static string pkgRequire( Interp interp, string pkgName, string version, bool exact )
    {
      VersionSatisfiesResult vsres;
      Package pkg;
      PkgAvail avail, best;
      string script;
      StringBuilder sbuf;
      int pass, result;

      // Do extra check to make sure that version is not
      // null when the exact flag is set to true.

      if ( (System.Object)version == null && exact )
      {
        throw new TclException( interp, "conflicting arguments : version == null and exact == true" );
      }

      // Before we can compare versions the version string
      // must be verified but if it is null we are just looking
      // for the latest version so skip the check in this case.

      if ( (System.Object)version != null )
      {
        checkVersion( interp, version );
      }

      // It can take up to three passes to find the package:  one pass to
      // run the "package unknown" script, one to run the "package ifneeded"
      // script for a specific version, and a final pass to lookup the
      // package loaded by the "package ifneeded" script.

      vsres = new VersionSatisfiesResult();
      for ( pass = 1; ; pass++ )
      {
        pkg = findPackage( interp, pkgName );
        if ( (System.Object)pkg.version != null )
        {
          break;
        }

        // The package isn't yet present.  Search the list of available
        // versions and invoke the script for the best available version.

        best = null;
        for ( avail = pkg.avail; avail != null; avail = avail.next )
        {
          if ( ( best != null ) && ( compareVersions( avail.version, best.version, null ) <= 0 ) )
          {
            continue;
          }
          if ( (System.Object)version != null )
          {
            result = compareVersions( avail.version, version, vsres );
            if ( ( result != 0 ) && exact )
            {
              continue;
            }
            if ( !vsres.satisfies )
            {
              continue;
            }
          }
          best = avail;
        }
        if ( best != null )
        {
          // We found an ifneeded script for the package.  Be careful while
          // executing it:  this could cause reentrancy, so (a) protect the
          // script itself from deletion and (b) don't assume that best
          // will still exist when the script completes.

          script = best.script;
          try
          {
            interp.eval( script, TCL.EVAL_GLOBAL );
          }
          catch ( TclException e )
          {
            interp.addErrorInfo( "\n    (\"package ifneeded\" script)" );

            // Throw the error with new info added to errorInfo.

            throw;
          }
          interp.resetResult();
          pkg = findPackage( interp, pkgName );
          break;
        }

        // Package not in the database.  If there is a "package unknown"
        // command, invoke it (but only on the first pass;  after that,
        // we should not get here in the first place).

        if ( pass > 1 )
        {
          break;
        }
        script = interp.packageUnknown;
        if ( (System.Object)script != null )
        {
          sbuf = new StringBuilder();
          try
          {
            Util.appendElement( interp, sbuf, script );
            Util.appendElement( interp, sbuf, pkgName );
            if ( (System.Object)version == null )
            {
              Util.appendElement( interp, sbuf, "" );
            }
            else
            {
              Util.appendElement( interp, sbuf, version );
            }
            if ( exact )
            {
              Util.appendElement( interp, sbuf, "-exact" );
            }
          }
          catch ( TclException e )
          {
            throw new TclRuntimeError( "unexpected TclException: " + e.Message );
          }
          try
          {
            interp.eval( sbuf.ToString(), TCL.EVAL_GLOBAL );
          }
          catch ( TclException e )
          {
            interp.addErrorInfo( "\n    (\"package unknown\" script)" );

            // Throw the first exception.

            throw;
          }
          interp.resetResult();
        }
      }
      if ( (System.Object)pkg.version == null )
      {
        sbuf = new StringBuilder();
        sbuf.Append( "can't find package " + pkgName );
        if ( (System.Object)version != null )
        {
          sbuf.Append( " " + version );
        }
        throw new TclException( interp, sbuf.ToString() );
      }

      // At this point we know that the package is present.  Make sure that the
      // provided version meets the current requirement.

      if ( (System.Object)version == null )
      {
        return pkg.version;
      }

      result = compareVersions( pkg.version, version, vsres );
      if ( ( vsres.satisfies && !exact ) || ( result == 0 ) )
      {
        return pkg.version;
      }

      // If we have a version conflict we throw a TclException.

      throw new TclException( interp, "version conflict for package \"" + pkgName + "\": have " + pkg.version + ", need " + version );
    }
    internal static string pkgPresent( Interp interp, string pkgName, string version, bool exact )
    {
      Package pkg;
      VersionSatisfiesResult vsres = new VersionSatisfiesResult();
      int result;

      pkg = (Package)interp.packageTable[pkgName];
      if ( pkg != null )
      {
        if ( (System.Object)pkg.version != null )
        {

          // At this point we know that the package is present.  Make sure
          // that the provided version meets the current requirement.

          if ( (System.Object)version == null )
          {
            return pkg.version;
          }
          result = compareVersions( pkg.version, version, vsres );
          if ( ( vsres.satisfies && !exact ) || ( result == 0 ) )
          {
            return pkg.version;
          }
          throw new TclException( interp, "version conflict for package \"" + pkgName + "\": have " + pkg.version + ", need " + version );
        }
      }

      if ( (System.Object)version != null )
      {
        throw new TclException( interp, "package " + pkgName + " " + version + " is not present" );
      }
      else
      {
        throw new TclException( interp, "package " + pkgName + " is not present" );
      }
    }
    public TCL.CompletionCode cmdProc( Interp interp, TclObject[] objv )
    {
      VersionSatisfiesResult vsres;
      Package pkg;
      PkgAvail avail;
      PkgAvail prev;
      string version;
      string pkgName;
      string key;
      string cmd;
      string ver1, ver2;
      StringBuilder sbuf;
      IDictionaryEnumerator enum_Renamed;
      int i, opt, exact;
      bool once;

      if ( objv.Length < 2 )
      {
        throw new TclNumArgsException( interp, 1, objv, "option ?arg arg ...?" );
      }
      opt = TclIndex.get( interp, objv[1], validCmds, "option", 0 );
      switch ( opt )
      {

        case OPT_FORGET:
          {
            // Forget takes 0 or more arguments.

            for ( i = 2; i < objv.Length; i++ )
            {
              // We do not need to check to make sure
              // package name is "" because it would not
              // be in the hash table so name will be ignored.


              pkgName = objv[i].ToString();
              pkg = (Package)interp.packageTable[pkgName];

              // If this package does not exist, go to next one.

              if ( pkg == null )
              {
                continue;
              }
              SupportClass.HashtableRemove( interp.packageTable, pkgName );
              while ( pkg.avail != null )
              {
                avail = pkg.avail;
                pkg.avail = avail.next;
                avail = null;
              }
              pkg = null;
            }
            return TCL.CompletionCode.RETURN;
          }

        case OPT_IFNEEDED:
          {
            if ( ( objv.Length < 4 ) || ( objv.Length > 5 ) )
            {
              throw new TclNumArgsException( interp, 1, objv, "ifneeded package version ?script?" );
            }
            pkgName = objv[2].ToString();
            version = objv[3].ToString();

            // Verify that this version string is valid.

            checkVersion( interp, version );
            if ( objv.Length == 4 )
            {
              pkg = (Package)interp.packageTable[pkgName];
              if ( pkg == null )
                return TCL.CompletionCode.RETURN;
            }
            else
            {
              pkg = findPackage( interp, pkgName );
            }
            for ( avail = pkg.avail, prev = null; avail != null; prev = avail, avail = avail.next )
            {
              if ( compareVersions( avail.version, version, null ) == 0 )
              {
                if ( objv.Length == 4 )
                {
                  // If doing a query return current script.

                  interp.setResult( avail.script );
                  return TCL.CompletionCode.RETURN;
                }

                // We matched so we must be setting the script.

                break;
              }
            }

            // When we do not match on a query return nothing.

            if ( objv.Length == 4 )
            {
              return TCL.CompletionCode.RETURN;
            }
            if ( avail == null )
            {
              avail = new PkgAvail();
              avail.version = version;
              if ( prev == null )
              {
                avail.next = pkg.avail;
                pkg.avail = avail;
              }
              else
              {
                avail.next = prev.next;
                prev.next = avail;
              }
            }

            avail.script = objv[4].ToString();
            return TCL.CompletionCode.RETURN;
          }

        case OPT_NAMES:
          {
            if ( objv.Length != 2 )
            {
              throw new TclNumArgsException( interp, 1, objv, "names" );
            }

            try
            {
              sbuf = new StringBuilder();
              enum_Renamed = interp.packageTable.GetEnumerator();
              once = false;
              while ( enum_Renamed.MoveNext() )
              {
                once = true;
                key = ( (string)enum_Renamed.Current );
                pkg = (Package)enum_Renamed.Value;
                if ( ( (System.Object)pkg.version != null ) || ( pkg.avail != null ) )
                {
                  Util.appendElement( interp, sbuf, key );
                }
              }
              if ( once )
              {
                interp.setResult( sbuf.ToString() );
              }
            }
            catch ( TclException e )
            {

              throw new TclRuntimeError( "unexpected TclException: " + e );
            }
            return TCL.CompletionCode.RETURN;
          }

        case OPT_PRESENT:
          {
            if ( objv.Length < 3 )
            {
              throw new TclNumArgsException( interp, 2, objv, "?-exact? package ?version?" );
            }

            if ( objv[2].ToString().Equals( "-exact" ) )
            {
              exact = 1;
            }
            else
            {
              exact = 0;
            }

            version = null;
            if ( objv.Length == ( 4 + exact ) )
            {

              version = objv[3 + exact].ToString();
              checkVersion( interp, version );
            }
            else if ( ( objv.Length != 3 ) || ( exact == 1 ) )
            {
              throw new TclNumArgsException( interp, 2, objv, "?-exact? package ?version?" );
            }
            if ( exact == 1 )
            {

              version = pkgPresent( interp, objv[3].ToString(), version, true );
            }
            else
            {

              version = pkgPresent( interp, objv[2].ToString(), version, false );
            }
            interp.setResult( version );
            break;
          }

        case OPT_PROVIDE:
          {
            if ( ( objv.Length < 3 ) || ( objv.Length > 4 ) )
            {
              throw new TclNumArgsException( interp, 1, objv, "provide package ?version?" );
            }
            if ( objv.Length == 3 )
            {

              pkg = (Package)interp.packageTable[objv[2].ToString()];
              if ( pkg != null )
              {
                if ( (System.Object)pkg.version != null )
                {
                  interp.setResult( pkg.version );
                }
              }
              return TCL.CompletionCode.RETURN;
            }

            pkgProvide( interp, objv[2].ToString(), objv[3].ToString() );
            return TCL.CompletionCode.RETURN;
          }

        case OPT_REQUIRE:
          {
            if ( ( objv.Length < 3 ) || ( objv.Length > 5 ) )
            {
              throw new TclNumArgsException( interp, 1, objv, "require ?-exact? package ?version?" );
            }

            if ( objv[2].ToString().Equals( "-exact" ) )
            {
              exact = 1;
            }
            else
            {
              exact = 0;
            }
            version = null;
            if ( objv.Length == ( 4 + exact ) )
            {

              version = objv[3 + exact].ToString();
              checkVersion( interp, version );
            }
            else if ( ( objv.Length != 3 ) || ( exact == 1 ) )
            {
              throw new TclNumArgsException( interp, 1, objv, "require ?-exact? package ?version?" );
            }
            if ( exact == 1 )
            {

              version = pkgRequire( interp, objv[3].ToString(), version, true );
            }
            else
            {

              version = pkgRequire( interp, objv[2].ToString(), version, false );
            }
            interp.setResult( version );
            return TCL.CompletionCode.RETURN;
          }

        case OPT_UNKNOWN:
          {
            if ( objv.Length > 3 )
            {
              throw new TclNumArgsException( interp, 1, objv, "unknown ?command?" );
            }
            if ( objv.Length == 2 )
            {
              if ( (System.Object)interp.packageUnknown != null )
              {
                interp.setResult( interp.packageUnknown );
              }
            }
            else if ( objv.Length == 3 )
            {
              interp.packageUnknown = null;

              cmd = objv[2].ToString();
              if ( cmd.Length > 0 )
              {
                interp.packageUnknown = cmd;
              }
            }
            return TCL.CompletionCode.RETURN;
          }

        case OPT_VCOMPARE:
          {
            if ( objv.Length != 4 )
            {
              throw new TclNumArgsException( interp, 1, objv, "vcompare version1 version2" );
            }

            ver1 = objv[2].ToString();

            ver2 = objv[3].ToString();
            checkVersion( interp, ver1 );
            checkVersion( interp, ver2 );
            interp.setResult( compareVersions( ver1, ver2, null ) );
            return TCL.CompletionCode.RETURN;
          }

        case OPT_VERSIONS:
          {
            if ( objv.Length != 3 )
            {
              throw new TclNumArgsException( interp, 1, objv, "versions package" );
            }

            pkg = (Package)interp.packageTable[objv[2].ToString()];
            if ( pkg != null )
            {
              try
              {
                sbuf = new StringBuilder();
                once = false;
                for ( avail = pkg.avail; avail != null; avail = avail.next )
                {
                  once = true;
                  Util.appendElement( interp, sbuf, avail.version );
                }
                if ( once )
                {
                  interp.setResult( sbuf.ToString() );
                }
              }
              catch ( TclException e )
              {
                throw new TclRuntimeError( "unexpected TclException: " + e.Message, e );
              }
            }
            return TCL.CompletionCode.RETURN;
          }

        case OPT_VSATISFIES:
          {
            if ( objv.Length != 4 )
            {
              throw new TclNumArgsException( interp, 1, objv, "vsatisfies version1 version2" );
            }


            ver1 = objv[2].ToString();

            ver2 = objv[3].ToString();
            checkVersion( interp, ver1 );
            checkVersion( interp, ver2 );
            vsres = new VersionSatisfiesResult();
            compareVersions( ver1, ver2, vsres );
            interp.setResult( vsres.satisfies );
            return TCL.CompletionCode.RETURN;
          }

        default:
          {
            throw new TclRuntimeError( "TclIndex.get() error" );
          }

      } // end switch(opt)
      return TCL.CompletionCode.RETURN;
    }
    private static Package findPackage( Interp interp, string pkgName )
    {
      Package pkg;

      // check package name to make sure it is not null or "".

      if ( (System.Object)pkgName == null || pkgName.Length == 0 )
      {
        throw new TclException( interp, "expected package name but got \"\"" );
      }

      pkg = (Package)interp.packageTable[pkgName];
      if ( pkg == null )
      {
        // We should add a package with this name.

        pkg = new Package();
        SupportClass.PutElement( interp.packageTable, pkgName, pkg );
      }
      return pkg;
    }
    private static void checkVersion( Interp interp, string version )
    {
      int i, len;
      char c;
      bool error = true;

      try
      {
        if ( ( (System.Object)version == null ) || ( version.Length == 0 ) )
        {
          version = "";
          return;
        }
        if ( !System.Char.IsDigit( version[0] ) )
        {
          return;
        }
        len = version.Replace( ".C#", "" ).Replace( "(C#)", "" ).Replace( "C#", "" ).Length;
        for ( i = 1; i < len; i++ )
        {
          c = version[i];
          if ( !System.Char.IsDigit( c ) && ( c != '.' ) )
          {
            return;
          }
        }
        if ( version[len - 1] == '.' )
        {
          return;
        }
        error = false;
      }
      finally
      {
        if ( error )
        {
          throw new TclException( interp, "expected version number but got \"" + version + "\"" );
        }
      }
    }
    private static int compareVersions( string v1, string v2, VersionSatisfiesResult vsres )
    {
      int i;
      int max;
      int n1 = 0;
      int n2 = 0;
      bool thisIsMajor = true;
      string[] v1ns;
      string[] v2ns;

      // Each iteration of the following loop processes one number from
      // each string, terminated by a ".".  If those numbers don't match
      // then the comparison is over;  otherwise, we loop back for the
      // next number.


      // This should never happen because null strings would not
      // have gotten past the version verify.

      if ( ( (System.Object)v1 == null ) || ( (System.Object)v2 == null ) )
      {
        throw new TclRuntimeError( "null version in package version compare" );
      }
      v1ns = split( v1, '.' );
      v2ns = split( v2, '.' );

      // We are sure there is at least one string in each array so 
      // this should never happen.

      if ( v1ns.Length == 0 || v2ns.Length == 0 )
      {
        throw new TclRuntimeError( "version length is 0" );
      }
      if ( v1ns.Length > v2ns.Length )
      {
        max = v1ns.Length;
      }
      else
      {
        max = v2ns.Length;
      }

      for ( i = 0; i < max; i++ )
      {
        n1 = n2 = 0;

        // Grab number from each version ident if version spec
        // ends the use a 0 as value.

        try
        {
          if ( i < v1ns.Length )
          {
            n1 = System.Int32.Parse( v1ns[i] );
          }
          if ( i < v2ns.Length )
          {
            n2 = System.Int32.Parse( v2ns[i] );
          }
        }
        catch ( System.FormatException ex )
        {
          throw new TclRuntimeError( "NumberFormatException for package versions \"" + v1 + "\" or \"" + v2 + "\"" );
        }

        // Compare and go on to the next version number if the
        // current numbers match.

        if ( n1 != n2 )
        {
          break;
        }
        thisIsMajor = false;
      }
      if ( vsres != null )
      {
        vsres.satisfies = ( ( n1 == n2 ) || ( ( n1 > n2 ) && !thisIsMajor ) );
      }
      if ( n1 > n2 )
      {
        return 1;
      }
      else if ( n1 == n2 )
      {
        return 0;
      }
      else
      {
        return -1;
      }
    }
    internal static string[] split( string in_Renamed, char splitchar )
    {
      ArrayList words;
      string[] ret;
      int i;
      int len;
      char[] str;
      int wordstart = 0;

      // Create an array that is as big as the input
      // str plus one for an extra split char.

      len = in_Renamed.Length;
      str = new char[len + 1];
      SupportClass.GetCharsFromString( in_Renamed, 0, len, ref str, 0 );
      str[len++] = splitchar;
      words = new ArrayList( 5 );

      for ( i = 0; i < len; i++ )
      {

        // Compare this char to the split char
        // if they are the same the we need to
        // add the last word to the array.

        if ( str[i] == splitchar )
        {
          if ( wordstart <= ( i - 1 ) )
          {
            words.Add( new string( str, wordstart, i - wordstart ) );
          }
          wordstart = ( i + 1 );
        }
      }

      // Create an array that is as big as the number
      // of elements in the vector, copy over and return.

      ret = new string[words.Count];
      words.CopyTo( ret );
      return ret;
    }








    // If compare versions is called with a third argument then one of
    // these structures needs to be created and passed in


    internal class VersionSatisfiesResult
    {
      internal bool satisfies = false;
    }

    // Each invocation of the "package ifneeded" command creates a class
    // of the following type, which is used to load the package into the
    // interpreter if it is requested with a "package require" command.

    internal class PkgAvail
    {
      internal string version = null; // Version string.
      internal string script = null; // Script to invoke to provide this package version
      internal PkgAvail next = null; // Next in list of available package versions
    }



    // For each package that is known in any way to an interpreter, there
    // is one record of the following type.  These records are stored in
    // the "packageTable" hash table in the interpreter, keyed by
    // package name such as "Tk" (no version number).

    internal class Package
    {
      internal string version = null; // Version that has been supplied in this
      // interpreter via "package provide"
      // null means the package doesn't
      // exist in this interpreter yet.

      internal PkgAvail avail = null; // First in list of all available package versions
    }
  } //end of class PackageCmd
}