wasCSharpSQLite – Rev 3
?pathlinks?
/*
* 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
}