/*
 * mkGeneric 1.3
 * -------------
 *
 * Please see the web pages for releases and documentation.
 *
 * Author: Michael Kraus
 *         mailto:mmg_kraus@compuserve.com
 *         http://ourworld.compuserve.com/homepages/mmg_kraus
 *
 * Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose and without fee is hereby granted.
 * The author makes no representations about the suitability of this
 * software for any purpose.  It is provided "as is" without express
 * or implied warranty.  By use of this software the user agrees to
 * indemnify and hold harmless the author from any claims or
 * liability for loss arising out of such use.
 *
 */

/* required to built a dll using stubs. should be a compiler option */
/* #define USE_TCL_STUBS */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <tcl.h>

#ifndef TRUE
#  define TRUE  1
#  define FALSE 0
#endif

/* copied from sun's example.c */
#ifdef __WIN32__
#if defined(__WIN32__)
#   define WIN32_LEAN_AND_MEAN
#   include <windows.h>
#   undef WIN32_LEAN_AND_MEAN
#   if defined(_MSC_VER)
#       define EXPORT(a,b) __declspec(dllexport) a b
#       define DllEntryPoint DllMain
#   else
#       if defined(__BORLANDC__)
#           define EXPORT(a,b) a _export b
#       else
#           define EXPORT(a,b) a b
#       endif
#   endif
#else
#   define EXPORT(a,b) a b
#endif

EXTERN EXPORT(int,Mkgeneric_Init) _ANSI_ARGS_((Tcl_Interp *interp));
EXTERN EXPORT(int,Mkgeneric_SafeInit) _ANSI_ARGS_((Tcl_Interp *interp));
BOOL APIENTRY DllEntryPoint(HINSTANCE hInst, DWORD reason, LPVOID reserved)
{
    return TRUE;
}
#endif

/* mkGeneric version number */
#define _VERSION           "1.3"

/* some acronyms for popular Tcl_xxx functions */
#define _NSO(pcText)       Tcl_NewStringObj( pcText, -1 )
#define _SSO(pO,pcText)    Tcl_SetStringObj( pO, pcText, -1 )
#define _GSO(pO)           Tcl_GetStringFromObj( pO, NULL )

#define _NIO(iVal)         Tcl_NewIntObj( iVal )
#define _SIO(pO,iVal)      Tcl_SetIntObj( pO, iVal )
#define _GIO(pO,piVal)     Tcl_GetIntFromObj( pI, pO, piVal )

#define _NBO(bVal)         Tcl_NewBooleanObj( bVal )
#define _SBO(pO,bVal)      Tcl_SetBooleanObj( pO, bVal )
#define _GBO(pO,pbVal)     Tcl_GetBooleanFromObj( pI, pO, pbVal )

#define _NDO(fVal)         Tcl_NewDoubleObj( fVal )
#define _SDO(pO,fVal)      Tcl_SetDoubleObj( pO, fVal )
#define _GDO(pO,pfVal)     Tcl_GetDoubleFromObj( pI, pO, pfVal )

#define _NAO(pcDt,iLen)    Tcl_NewByteArrayObj( pcDt, iLen )
#define _SAO(pO,pcDt,iLen) Tcl_SetByteArrayObj( pO, pcDt, iLen )
#define _GAO(pO,piLen)     Tcl_GetByteArrayFromObj( pO, piLen )

#define _LOAL(pO,pNewO)    Tcl_ListObjAppendList( pI, pO, pNewO )
#define _LOAE(pO,pNewO)    Tcl_ListObjAppendElement( pI, pO, pNewO )
#define _LOGL(pO,piLen)    Tcl_ListObjLength( pI, pO, piLen )
#define _LOGI(pO,iI,poE)   Tcl_ListObjIndex( pI, pO, iI, poE )
#define _LOGE(pO,piC,ppV)  Tcl_ListObjGetElements( pI, pO, piC, ppV )
#define _LORE(pO,iPos,poE) Tcl_ListObjReplace( pI, pO, iPos, 1, 1, poE )
#define _LODE(pO,iPos)     Tcl_ListObjReplace( pI, pO, iPos, 1, 0, NULL )

#define _OSV(po1,po2,poV)  Tcl_ObjSetVar2( pI, po1, po2, poV, TCL_LEAVE_ERR_MSG )
#define _OGV(po1,po2)      Tcl_ObjGetVar2( pI, po1, po2, TCL_LEAVE_ERR_MSG )
#define _OUV(po1,po2)      Tcl_UnsetVar2( pI, _GSO(po1), (po2==NULL)?NULL:_GSO(po2), TCL_LEAVE_ERR_MSG )
#define _OSVG(po1,po2,poV) Tcl_ObjSetVar2( pI, po1, po2, poV, TCL_GLOBAL_ONLY )
#define _OGVG(po1,po2)     Tcl_ObjGetVar2( pI, po1, po2, TCL_GLOBAL_ONLY )
#define _OUVG(po1,po2)     Tcl_UnsetVar2( pI, _GSO(po1), (po2==NULL)?NULL:_GSO(po2), TCL_GLOBAL_ONLY )
#define _OGVG2(pc1,pc2)    Tcl_GetVar2Ex( pI, pc1, pc2, TCL_GLOBAL_ONLY )

#define _NOB               Tcl_NewObj()
#define _DOB               Tcl_DuplicateObj
#define _SOB(pO)           Tcl_IsShared( pO )? Tcl_DuplicateObj( pO ):pO
#define _ASO               Tcl_AppendStringsToObj

#define _DRC               Tcl_DecrRefCount
#define _IRC               Tcl_IncrRefCount

#define _SEC(pO)           Tcl_SetObjErrorCode( pI, pO )
#define _AEI(pC,iL)        Tcl_AddObjErrorInfo( pI, pC, iL )

#define _GOR               Tcl_GetObjResult( pI )
#define _SOR(pO)           Tcl_SetObjResult( pI, pO )
#define _ROR               Tcl_ResetResult( pI )

#define _GIFO(pO,pA,pcTxt,piRes) Tcl_GetIndexFromObj( pI, pO, pA, pcTxt, 0, piRes )
#define _WNA(objc,pcText)        ( Tcl_WrongNumArgs( pI, objc, objv, pcText ), TCL_ERROR )

/* my very own exception handling */
#define try( Expr, Excep ) { if( Expr != TCL_OK ) throw Excep; }
#define throw              goto
#define catch

/* the following is needed for the sha-256 algorithm */
#define _R(x,n)     (((x)>>(n))|((x)<<(32-(n))))
#define _S(x,n)     ((x)>>(n))
#define _CH(x,y,z)  (((x)&(y))|(~(x)&(z)))
#define _MAJ(x,y,z) (((x)&(y))|((x)&(z))|((y)&(z)))

#define _SIG0(x) (_R(x, 2)^_R(x,13)^_R(x,22))
#define _SIG1(x) (_R(x, 6)^_R(x,11)^_R(x,25))
#define _sig0(x) (_R(x, 7)^_R(x,18)^_S(x, 3))
#define _sig1(x) (_R(x,17)^_R(x,19)^_S(x,10))

/* the function prototypes. */
int Mkgeneric_Init( Tcl_Interp * );
int Mkgeneric_SafeInit( Tcl_Interp * );

int Mkg_Round2Func( ClientData, Tcl_Interp *, Tcl_Value *, Tcl_Value * );
int Mkg_MinMaxFunc( ClientData, Tcl_Interp *, Tcl_Value *, Tcl_Value * );
int Mkg_IsTypeFunc( ClientData, Tcl_Interp *, Tcl_Value *, Tcl_Value * );
int Mkg_IsEvenFunc( ClientData, Tcl_Interp *, Tcl_Value *, Tcl_Value * );
int Mkg_ConstFunc ( ClientData, Tcl_Interp *, Tcl_Value *, Tcl_Value * );

int Mkg_ThrowCmd   ( ClientData, Tcl_Interp *, int, Tcl_Obj *CONST[] );
int Mkg_TryCmd     ( ClientData, Tcl_Interp *, int, Tcl_Obj *CONST[] );
int Mkg_DecodeCmd  ( ClientData, Tcl_Interp *, int, Tcl_Obj *CONST[] );
int Mkg_CompleteCmd( ClientData, Tcl_Interp *, int, Tcl_Obj *CONST[] );
int Mkg_LinlistCmd ( ClientData, Tcl_Interp *, int, Tcl_Obj *CONST[] );
int Mkg_LdeleteCmd ( ClientData, Tcl_Interp *, int, Tcl_Obj *CONST[] );
int Mkg_LextendCmd ( ClientData, Tcl_Interp *, int, Tcl_Obj *CONST[] );
int Mkg_LshrinkCmd ( ClientData, Tcl_Interp *, int, Tcl_Obj *CONST[] );
int Mkg_LchangeCmd ( ClientData, Tcl_Interp *, int, Tcl_Obj *CONST[] );
int Mkg_LnextCmd   ( ClientData, Tcl_Interp *, int, Tcl_Obj *CONST[] );
int Mkg_LassignCmd ( ClientData, Tcl_Interp *, int, Tcl_Obj *CONST[] );
int Mkg_LevalCmd   ( ClientData, Tcl_Interp *, int, Tcl_Obj *CONST[] );
int Mkg_LstatCmd   ( ClientData, Tcl_Interp *, int, Tcl_Obj *CONST[] );
int Mkg_LinterCmd  ( ClientData, Tcl_Interp *, int, Tcl_Obj *CONST[] );
int Mkg_LunionCmd  ( ClientData, Tcl_Interp *, int, Tcl_Obj *CONST[] );
int Mkg_LmirrorCmd ( ClientData, Tcl_Interp *, int, Tcl_Obj *CONST[] );
int Mkg_LoopCmd    ( ClientData, Tcl_Interp *, int, Tcl_Obj *CONST[] );
int Mkg_DoCmd      ( ClientData, Tcl_Interp *, int, Tcl_Obj *CONST[] );
int Mkg_FincrCmd   ( ClientData, Tcl_Interp *, int, Tcl_Obj *CONST[] );
int Mkg_OptionsCmd ( ClientData, Tcl_Interp *, int, Tcl_Obj *CONST[] );
int Mkg_HexdumpCmd ( ClientData, Tcl_Interp *, int, Tcl_Obj *CONST[] );
int Mkg_EncryptCmd ( ClientData, Tcl_Interp *, int, Tcl_Obj *CONST[] );
int Mkg_DecryptCmd ( ClientData, Tcl_Interp *, int, Tcl_Obj *CONST[] );
int Mkg_ChecksumCmd( ClientData, Tcl_Interp *, int, Tcl_Obj *CONST[] );
int Mkg_HashCmd    ( ClientData, Tcl_Interp *, int, Tcl_Obj *CONST[] );
int Mkg_IgnoreCmd  ( ClientData, Tcl_Interp *, int, Tcl_Obj *CONST[] );

/* _MkgCompareInt
   comparison function for the qsort() in ldelete. compares two integers.
   see the documentation on qsort.
*/
int _MkgCompareInt( const void *piInt1, const void *piInt2 )
{
  if( *(int*)piInt1 < *(int*)piInt2 )
    return -1;
  if( *(int*)piInt1 > *(int*)piInt2 )
    return 1;
  else
    return 0;
}

/* _MkgCompareObj
   comparison function for the qsort() in lunion, linter. compares the
   string representation of two objects. see the documentation on qsort.
*/
int _MkgCompareObj( const void *poObj1, const void *poObj2 )
{
  return strcmp( _GSO( *(Tcl_Obj**)poObj1 ), _GSO( *(Tcl_Obj**)poObj2 ) );
}

/* _MkgGetIndex
   utility function. transforms an index like "end-4" into an integer. iLen
   is the length of the list to which the index refers. used for ldelete.
*/
int _MkgGetIndex( Tcl_Interp *pI, Tcl_Obj *poObj, int iLen, int *piIndex )
{
  int iIndex;

  if( _GIO( poObj, &iIndex ) == TCL_OK )
    *piIndex = iIndex;
  else if( ! strcmp( _GSO( poObj ), "end" ) || ! strcmp( _GSO( poObj ), "end-0" ) )
    *piIndex = iLen-1;
  else if( ! strncmp( _GSO( poObj ), "end-", 4 ) && ( iIndex = atoi( _GSO( poObj ) + 4 ) ) != 0 )
    *piIndex = iLen-iIndex-1;
  else
    throw eError;

  return TCL_OK;

  catch eError:
    _ROR;
    _ASO( _GOR, "bad index ", _GSO( poObj ), ": must be integer or end?-integer?", NULL );
    return TCL_ERROR;
}

/* _MkgTEAencrypt
   this is TEA, the "Tiny Encryption Algorithm". TEA is public domain,
   very simple and fast. TEA en/deciphers 64 bit with a 128 bit key.
   the code below is taken right off the internet, e.g.:
   http://www.ftp.cl.cam.ac.uk/ftp/papers/djw-rmn/djw-rmn-tea.html
   http://www.vader.brad.ac.uk/tea/tea.shtml
*/
void _MkgTEAencrypt( unsigned long *v, unsigned long *k )
{
  register unsigned long y=v[0], z=v[1], sum=0, delta=0x9E3779B9, n=32;

  while( n-- > 0 )
  {
    y += (z << 4 ^ z >> 5) + z ^ sum + k[sum&3];
    sum += delta;
    z += (y << 4 ^ y >> 5) + y ^ sum + k[sum>>11 & 3];
  }

  v[0]=y; v[1]=z;
}

/* _MkgTEAdecrypt
   see above.
*/
void _MkgTEAdecrypt( unsigned long *v, unsigned long *k )
{
  register unsigned long y=v[0], z=v[1], sum=0xC6EF3720, delta=0x9E3779B9, n=32;

  while( n-- > 0 )
  {
    z -= (y << 4 ^ y >> 5) + y ^ sum + k[sum>>11 & 3];
    sum -= delta;
    y -= (z << 4 ^ z >> 5) + z ^ sum + k[sum&3];
  }

  v[0]=y; v[1]=z;
}

/* _MkgConvertEndian
   helper function for the hash and crypt commands. converts an array of longs
   into big endian, if the host machine is little endian (such as pentiums).
   iBytes must be an integer multiple of 4.
*/
static void _MkgConvertEndian( void* pvData, int iBytes )
{
  static int bTested, bLittleEndian;
  unsigned long *plLongs = (unsigned long*) pvData;
  int i;

  if( ! bTested )
  {
    unsigned long lTest = 0x01;
    bLittleEndian = *((unsigned char*)(&lTest));
    bTested = 1;
  }

  if ( bLittleEndian )
    for ( i = 0; i < ( iBytes >> 2 ); i++ )
      plLongs[i] = ( ( ( plLongs[i] &     0xffU ) << 24 ) | ( ( plLongs[i] &     0xff00U ) <<  8 ) |
                     ( ( plLongs[i] & 0xff0000U ) >>  8 ) | ( ( plLongs[i] & 0xff000000U ) >> 24 ) );
}

/* _MkgShaTransform
   the transform function of sha-256. called by _MkgSha256.
   this is taken from source code that is publicly available on the internet.
*/
static void _MkgShaTransform( unsigned long *plH, unsigned char *pcM )
{
  int i;
  unsigned long lA = plH[ 0 ], lB = plH[ 1 ], lC = plH[ 2 ], lD = plH[ 3 ],
                lE = plH[ 4 ], lF = plH[ 5 ], lG = plH[ 6 ], lH = plH[ 7 ],
                lT1, lT2, plW[ 64 ];

  static unsigned long plK[] = {
    0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
    0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
    0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
    0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
    0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
    0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
    0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
    0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
  };

  memcpy( plW, pcM, 64 );

  for ( i = 16; i < 64; i++ )
    plW[i] = _sig1( plW[i-2] ) + plW[i-7] + _sig0( plW[i-15] ) + plW[i-16];

  for ( i = 0; i < 64; i++ )
  {
    lT1 = lH + _SIG1( lE ) + _CH( lE, lF, lG ) + plK[i] + plW[i];
    lT2 = _SIG0( lA ) + _MAJ( lA, lB, lC );
    lH = lG; lG = lF; lF = lE; lE = lD + lT1;
    lD = lC; lC = lB; lB = lA; lA = lT1 + lT2;
  }

  plH[0] += lA; plH[1] += lB; plH[2] += lC; plH[3] += lD;
  plH[4] += lE; plH[5] += lF; plH[6] += lG; plH[7] += lH;
}

/* _MkgSha256
   the actual sha-256 implementation. normally this is split up into several
   functions (init, update, finish, digest). this here is a combination of
   all of them. some simplifications were possible. this is derived from
   source code that is publicly available on the internet.
*/
void _MkgSha256( char *pcData, int iBytes, char *pcResult )
{
  int iMlen;
  unsigned char pcM[64];
  unsigned long plBits[2] = { 0, 0 };
  unsigned long plH[8] = {
    0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19
  };

  plBits[0] = iBytes >> 29;
  plBits[1] = iBytes << 3;

  while (1)
  {
    iMlen = ( iBytes < 64 )? iBytes : 64;
    memcpy( pcM, pcData, iMlen );
    iBytes -= iMlen;
    pcData += iMlen;

    if( iMlen < 64 ) break;

    _MkgConvertEndian( pcM, 64 );
    _MkgShaTransform( plH, pcM );
  }

  pcM[iMlen] = 128;

  if ( ++iMlen < 56 )
  {
    memset( pcM + iMlen, 0, 56 - iMlen );
    _MkgConvertEndian( pcM, 56 );
  }
  else
  {
    memset( pcM + iMlen, 0, 64 - iMlen );
    _MkgConvertEndian( pcM, 64 );
    _MkgShaTransform( plH, pcM );
    memset( pcM, 0, 56 );
  }

  memcpy( pcM + 56, (void*)plBits, 8 );
  _MkgShaTransform( plH, pcM );

  memcpy( pcResult, plH, 32 );
  _MkgConvertEndian( pcResult, 32 );
}

/* Mkg_Round2Func
   implements the math functions round2, floor2 and ceil2. (int)pC is 2 for
   ceil2, 1 for floor2, 0 for round2. if both parameters are integers, the
   result will be an integer, otherwise a double.
*/
int Mkg_Round2Func( ClientData pC, Tcl_Interp *pI, Tcl_Value *psArgs, Tcl_Value *psResult )
{
  double f1, f2, fResult;

  f1 = (psArgs[0].type==TCL_DOUBLE)? psArgs[0].doubleValue:psArgs[0].intValue;
  f2 = (psArgs[1].type==TCL_DOUBLE)? psArgs[1].doubleValue:psArgs[1].intValue;

  switch ( (int)pC )
  {
    case 0: fResult = floor((f1+f2/2)/f2)*f2; break;
    case 1: fResult = floor(f1/f2)*f2;        break;
    case 2: fResult = ceil(f1/f2)*f2;         break;
  }

  if( psArgs[0].type == TCL_DOUBLE || psArgs[1].type == TCL_DOUBLE )
  {
    psResult->type = TCL_DOUBLE;
    psResult->doubleValue = fResult;
  }
  else
  {
    psResult->type = TCL_INT;
    psResult->intValue = (int)fResult;
  }

  return TCL_OK;
}

/* Mkg_MinMaxFunc
   implements the math functions min and max. if max has been called,
   (int)pC is 1, otherwise 0. if both parameters are integers, the
   result will be an integer, otherwise a double.
*/
int Mkg_MinMaxFunc( ClientData pC, Tcl_Interp *pI, Tcl_Value *psArgs, Tcl_Value *psResult )
{
  double f1, f2, fResult;

  f1 = (psArgs[0].type==TCL_DOUBLE)? psArgs[0].doubleValue:psArgs[0].intValue;
  f2 = (psArgs[1].type==TCL_DOUBLE)? psArgs[1].doubleValue:psArgs[1].intValue;

  if( (int)pC )
    fResult = (f1>f2)? f1:f2;
  else
    fResult = (f1<f2)? f1:f2;

  if( psArgs[0].type == TCL_DOUBLE || psArgs[1].type == TCL_DOUBLE )
  {
    psResult->type = TCL_DOUBLE;
    psResult->doubleValue = fResult;
  }
  else
  {
    psResult->type = TCL_INT;
    psResult->intValue = (int)fResult;
  }

  return TCL_OK;
}

/* Mkg_IsTypeFunc
   implements the math functions isint and isdouble. if isint has been called,
   (int)pC is 1, otherwise 0. returns 1, if the value is of the given type,
   otherwise 0.
*/
int Mkg_IsTypeFunc( ClientData pC, Tcl_Interp *pI, Tcl_Value *psArgs, Tcl_Value *psResult )
{
  psResult->type = TCL_INT;

  if( (int)pC )
    psResult->intValue = (psArgs[0].type==TCL_DOUBLE)? 0:1;
  else
    psResult->intValue = (psArgs[0].type==TCL_DOUBLE)? 1:0;

  return TCL_OK;
}

/* Mkg_IsEvenFunc
   implements the math functions iseven and isodd. if iseven has been called,
   (int)pC is 1, otherwise 0. returns 1, if the value is even resp. odd,
   otherwise 0.
*/
int Mkg_IsEvenFunc( ClientData pC, Tcl_Interp *pI, Tcl_Value *psArgs, Tcl_Value *psResult )
{
  if( psArgs[0].type != TCL_INT )
    throw eError;

  psResult->type = TCL_INT;
  psResult->intValue = ( psArgs[0].intValue + (int)pC ) % 2;

  return TCL_OK;

  catch eError:
    _SSO( _GOR, "Operand must be an integer" );
    return TCL_ERROR;
}

/* Mkg_ConstFunc
   implements the pi and euler constants.
*/
int Mkg_ConstFunc( ClientData pC, Tcl_Interp *pI, Tcl_Value *psArgs, Tcl_Value *psResult )
{
  static int bInit;
  static double fPi, fE;

  if( ! bInit )
  {
    fPi = 2*acos(0);
    fE  = exp(1);
    bInit = TRUE;
  }

  psResult->type = TCL_DOUBLE;

  if( (int)pC )
    psResult->doubleValue = fE;
  else
    psResult->doubleValue = fPi;

  return TCL_OK;
}

/* Mkg_ThrowCmd
   implements the throw command, which is intented to be used in conjunction
   with the try command. throw always returns TCL_ERROR. this is similar to
   the standard "error" command, except that the error code is specified first
   and then an optional message string. also, if throw is called without any
   arguments, it tries to recover the information about the last error and
   throws this error again. note that the order of setting the 3 error
   information parts must not be changed: first the code, then the info, then
   the result.
*/
int Mkg_ThrowCmd( ClientData pC, Tcl_Interp *pI, int objc, Tcl_Obj *CONST objv[] )
{
  char *pcInfo;
  Tcl_Obj *poMsg;

  if( objc > 4 )
    return _WNA( 1, "?code? ?message? ?info?" );

  switch( objc )
  {
    case 1:
      /* first, copy any existing errorCode */
      _SEC( _OGVG2( "errorCode", NULL ) );

      /* now get the first line of the stack trace and append it again */
      pcInfo = _GSO( _OGVG2( "errorInfo", NULL ) );
      _AEI( pcInfo, strchr( pcInfo, '\n' ) - pcInfo );

      /* finally, if errorMsg was set by a previous try-catch, make it the new result */
      if( ( poMsg = _OGVG2( "errorMsg", NULL ) ) != NULL )
        _SOR( poMsg );

      break;

    case 2:
      _SEC( objv[1] );
      _ASO( _GOR, "Exception '", _GSO( objv[1] ), "' thrown.", NULL );
      break;

    case 3:
      _SEC( objv[1] );
      _SOR( objv[2] );
      break;

    case 4:
      _SEC( objv[1] );
      _AEI( _GSO( objv[3] ), -1 );
      _SOR( objv[2] );
      break;
  }

  return TCL_ERROR;
}

/* Mkg_TryCmd
   implements the try command. it first figures out if the syntax is correct.
   then evaluates the try-script. if it succeeds (i.e., returns with TCL_OK,
   TCL_RETURN, TCL_CONTINUE or TCL_BREAK), then it evals the finally-script
   and goes. if it fails with TCL_ERROR, then it sets the "artificial"
   variable "errorMsg" so that a catch-script can get the result that the
   try-script *would* have returned. then looks for the first element
   of the errorCode and tries to find a matching catch-exception. if it finds
   one, it evals the associated catch-script. if not, it evals the default
   catch-script, if any. then it evals the finally-script, if defined.
   if the catch-script failes, the finally-script is still executed, and then
   the error caused by the catch-script is propagated. if the finally-script
   fails, the error is propagated, too. it should all behave like in java.
*/
int Mkg_TryCmd( ClientData pC, Tcl_Interp *pI, int objc, Tcl_Obj *CONST objv[] )
{
  int      i, iLast, iObjc, iRes, iResF, bNoFinally;
  char    *pcLastKey, *pcInfo;
  Tcl_Obj **ppoObjv, *poFinally, *poDefault, *poCode, *poInfo, *poRes;

  if( objc == 1 || objc == 3 )
    return _WNA( 1, "script ?catch pattern script ...? ?catch script? ?finally script?" );

  /* assume no default-script and no finally-script */
  poFinally = poDefault = NULL;
  pcLastKey = _GSO( objv[objc-2] );

  if( ! ( objc % 3 ) )          /* default-catch and finally must be there */
  {
    if( strcmp( pcLastKey, "finally" ) )
      throw eFinally;

    poFinally = objv[objc-1];
    poDefault = objv[objc-3];
  }
  else if( ! ( ( objc - 1 ) % 3 ) )  /* either default or finally is given */
  {
    bNoFinally = strcmp( pcLastKey, "finally" );

    if( bNoFinally && strcmp( pcLastKey, "catch" ) )
      throw eFinally;

    if( bNoFinally )
      poDefault = objv[objc-1];
    else
      poFinally = objv[objc-1];
  }

  for( i = 2; i <= objc - 3; i += 3 )  /* now check for all catch keywords */
    if( strcmp( _GSO( objv[i] ), "catch" ) )
      throw eCatch;

  iRes = Tcl_EvalObj( pI, objv[1] );                /* eval the try script */

  if( iRes == TCL_ERROR )           /* error? try to find a catch-handler! */
  {
    /* store result in a var, so that a catch-script can get to it */
    Tcl_SetVar2Ex( pI, "errorMsg", NULL, _GOR, TCL_GLOBAL_ONLY );

    _LOGE( Tcl_GetVar2Ex( pI, "errorCode", NULL, TCL_GLOBAL_ONLY ), &iObjc, &ppoObjv );
    iLast = objc - ( poDefault? 4 : poFinally? 3 : 2 );

    /* compare the catch patterns with first element of errorCode */
    for( i = 3; i <= iLast; i += 3 )
      if( Tcl_StringMatch( _GSO( ppoObjv[0] ), _GSO( objv[i] ) ) )
        break;

    if( i <= iLast )                /* matching pattern found, eval script */
      iRes = Tcl_EvalObj( pI, objv[i+1] );
    else if( poDefault )   /* no pattern found, eval default catch, if any */
      iRes = Tcl_EvalObj( pI, poDefault );
  }

  if( poFinally )                       /* eval a given finally script now */
  {
    if( iRes == TCL_ERROR )   /* save error infos from try or catch script */
    {
      poCode = _OGVG2( "errorCode", NULL ); _IRC( poCode );
      poInfo = _OGVG2( "errorInfo", NULL ); _IRC( poInfo ); pcInfo = _GSO( poInfo );
      poRes  = _GOR;                        _IRC( poRes );
    }

    if( ( iResF = Tcl_EvalObj( pI, poFinally ) ) != TCL_OK )
    {                                               /* eval finally script */
      _DRC( poCode ); _DRC( poInfo ); _DRC( poRes );
      return iResF;        /* propagate TCL_ERROR, TCL_BREAK, TCL_CONTINUE */
    }

    if( iRes == TCL_ERROR )         /* restore the saved error information */
    {
      _SEC( poCode );                                  _DRC( poCode );
      _AEI( pcInfo, strchr( pcInfo, '\n' ) - pcInfo ); _DRC( poInfo );
      _SOR( poRes );                                   _DRC( poRes );
    }
  }

  return iRes;      /* propagate the result of the try or the catch script */

  catch eFinally:
    _SSO( _GOR, "Keyword 'finally' not found where expected." );
    return TCL_ERROR;
  catch eCatch:
    _SSO( _GOR, "Keyword 'catch' not found where expected." );
    return TCL_ERROR;
}

/* Mkg_DecodeCmd
   implements the decode command. each odd element in list is compared against
   expr. if it matches the next even element, i.e. the next element, is returned.
   if there is no match and a default value has been defined, it is returned,
   otherwise nothing.
*/
int Mkg_DecodeCmd( ClientData pC, Tcl_Interp *pI, int objc, Tcl_Obj *CONST objv[] )
{
  int       i, iObjc;
  char     *pcExpr;
  Tcl_Obj **ppoObjv;

  if( objc < 3 )
    return _WNA( 1, "expr list ?defaultValue?" );

  pcExpr = _GSO( objv[1] );

  try( _LOGE( objv[2], &iObjc, &ppoObjv ), eError );

  if( iObjc % 2 )
    throw eOddNumber;

  for( i = 0; i < iObjc; i+=2 )
  {
    if( ! strcmp( pcExpr, _GSO( ppoObjv[i] ) ) )
    {
      _SOR( ppoObjv[i+1] );
      break;
    }
  }

  if( i == iObjc && objc == 4 )
    _SOR( objv[3] );

  return TCL_OK;

  catch eError:
    return TCL_ERROR;
  catch eOddNumber:
    _SSO( _GOR, "List must have an even number of elements" );
    return TCL_ERROR;
}

/* Mkg_CompleteCmd
   implements the complete command. builds the required char** array for
   Tcl_GetIndexFromObj() and calls the latter. in case of a match, lets the
   result point to the matching element.
*/
int Mkg_CompleteCmd( ClientData pC, Tcl_Interp *pI, int objc, Tcl_Obj *CONST objv[] )
{
  int       i, iObjc, iMatch;
  char    **ppcValues;
  Tcl_Obj **ppoObjv;

  if( objc != 3 )
    return _WNA( 1, "expr list" );

  try( _LOGE( objv[2], &iObjc, &ppoObjv ), eError );
  ppcValues = (char**)ckalloc( ( iObjc + 1 ) * sizeof(char*) );

  for( i = 0; i < iObjc; i++ )
    ppcValues[i] = _GSO( ppoObjv[i] );

  ppcValues[iObjc] = NULL;

  try( _GIFO( objv[1], ppcValues, "value", &iMatch ), eNotFound );

  ckfree( (char*)ppcValues );
  _SOR( ppoObjv[iMatch] );

  return TCL_OK;

  catch eError:
    return TCL_ERROR;

  catch eNotFound:
    ckfree( (char*)ppcValues );
    return TCL_ERROR;
}

/* Mkg_LinlistCmd
   implements the linlist command. loops thru the given list in search of a
   match.
*/
int Mkg_LinlistCmd( ClientData pC, Tcl_Interp *pI, int objc, Tcl_Obj *CONST objv[] )
{
  int       iObjc;
  Tcl_Obj **ppoObjv;

  if( objc != 3 )
    return _WNA( 1, "list element" );

  try( _LOGE( objv[1], &iObjc, &ppoObjv ), eError );
  while( iObjc-- )
    if( ! strcmp( _GSO( ppoObjv[iObjc] ), _GSO( objv[2] ) ) )
      break;

  _SIO( _GOR, ( iObjc == -1 )? 0:1 );
  return TCL_OK;

  catch eError:
    return TCL_ERROR;
}

/* Mkg_LdeleteCmd
   implements the ldelete command.
*/
int Mkg_LdeleteCmd( ClientData pC, Tcl_Interp *pI, int objc, Tcl_Obj *CONST objv[] )
{
  int     *pIndexes, iLen, i;
  Tcl_Obj *poList;

  if( objc < 2 )
    return _WNA( 1, "list ?index ...?" );

  pIndexes = (int*)ckalloc( (objc-2) * sizeof(int) );
  poList = _SOB( objv[1] );
  try( _LOGL( poList, &iLen ), eError );

  for( i = 2; i < objc; i++ )
    try( _MkgGetIndex( pI, objv[i], iLen, pIndexes+i-2 ), eError );

  qsort( pIndexes, objc-2, sizeof(int), _MkgCompareInt );

  for( i = objc-3; i >= 0; i-- )
  {
    if( pIndexes[i] == pIndexes[i-1] && i > 0 )
      continue;

    if( pIndexes[i] >= 0 && pIndexes[i] < iLen )
      _LODE( poList, pIndexes[i] );
  }

  ckfree( (char*)pIndexes );
  _SOR( poList );

  return TCL_OK;

  catch eError:
    ckfree( (char*)pIndexes );
    return TCL_ERROR;
}

/* Mkg_LextendCmd
   implements the lextend command. searches each element in the given list
   for a string match with the given value. the list is copied into the
   result string, and, if no match was found, the value is appended.
*/
int Mkg_LextendCmd( ClientData pC, Tcl_Interp *pI, int objc, Tcl_Obj *CONST objv[] )
{
  int      i, j, iObjc;
  Tcl_Obj  *poList, **ppoObjv;

  if( objc < 2 )
    return _WNA( 1, "list ?value ...?" );

  poList = _SOB( objv[1] );

  for( i = 2; i < objc; i++ )
  {
    try( _LOGE( poList, &iObjc, &ppoObjv ), eError );

    for( j= 0; j < iObjc; j++ )
      if( ! strcmp( _GSO( ppoObjv[j] ) , _GSO( objv[i] ) ) )
        break;

    if( j == iObjc )
      _LOAE( poList, _SOB( objv[i] ) );
  }

  _SOR( poList );

  return TCL_OK;

  catch eError:
    return TCL_ERROR;
}

/* Mkg_LshrinkCmd
   implements the lshrink command. searches each element in the given list
   for a string match with the given value. any match is appended to
   a temporary list poHits. the second loop deletes all elements in poList
   whose positions are in the temporary list.
*/
int Mkg_LshrinkCmd( ClientData pC, Tcl_Interp *pI, int objc, Tcl_Obj *CONST objv[] )
{
  int      i, j, iHit, iObjc;
  Tcl_Obj  *poList, *poHits, **ppoObjv;

  if( objc < 2 )
    return _WNA( 1, "list ?value ...?" );

  poHits = _NOB;
  poList = _SOB( objv[1] );
  try( _LOGE( poList, &iObjc, &ppoObjv ), eError );

  for( i = 0; i < iObjc; i++ )
    for( j = 2; j < objc; j++ )
      if( ! strcmp( _GSO( ppoObjv[i] ) , _GSO( objv[j] ) ) )
      {
        try( _LOAE( poHits, _NIO( i ) ), eError );
        break;
      }

  try( _LOGE( poHits, &iObjc, &ppoObjv ), eError );

  for( i = iObjc-1; i >= 0; i-- )
  {
    try( _GIO( ppoObjv[i], &iHit ), eError );
    _LODE( poList, iHit );
  }

  _DRC( poHits );
  _SOR( poList );

  return TCL_OK;

  catch eError:
    return TCL_ERROR;
}

/* Mkg_LchangeCmd
   implements the lchange command. expects pairs of values. each value1 is
   searched in list and replaced with value2, for all given value pairs.
*/
int Mkg_LchangeCmd( ClientData pC, Tcl_Interp *pI, int objc, Tcl_Obj *CONST objv[] )
{
  int      i, j, iObjc;
  Tcl_Obj  *poList, **ppoObjv;

  if( objc < 2 || objc % 2 )
    return _WNA( 1, "list value1 value2 ?value1 value2 ...?" );

  poList = _SOB( objv[1] );
  try( _LOGE( poList, &iObjc, &ppoObjv ), eError );

  for( i = 0; i < iObjc; i++ )
    for( j = 2; j < objc; j += 2 )
      if( ! strcmp( _GSO( ppoObjv[i] ) , _GSO( objv[j] ) ) )
      {
        try( _LORE( poList, i, &(objv[j+1]) ), eError );
        break;
      }

  _SOR( poList );

  return TCL_OK;

  catch eError:
    return TCL_ERROR;
}

/* Mkg_LnextCmd
   implememts the lnext and lprev commands.
*/
int Mkg_LnextCmd( ClientData pC, Tcl_Interp *pI, int objc, Tcl_Obj *CONST objv[] )
{
  int i, iObjc, bNext;
  Tcl_Obj **ppoObjv;

  bNext = (int)pC;

  if( objc != 3 )
    return _WNA( 1, "list element" );

  _LOGE( objv[1], &iObjc, &ppoObjv );

  for( i = 0; i < iObjc; i++ )
    if( ! strcmp( _GSO( ppoObjv[i] ), _GSO( objv[2] ) ) )
      break;

  if( i == iObjc )
    throw eNotFound;

  if( bNext )
  {
    if( i < iObjc-1 )
      _SOR( ppoObjv[i+1] );
  }
  else
  {
    if( i > 0 )
      _SOR( ppoObjv[i-1] );
  }

  return TCL_OK;

  catch eNotFound:
    _ASO( _GOR, "element ", _GSO( objv[2] ), " not found in list", NULL );
    return TCL_ERROR;
}

/* Mkg_LassignCmd
   implememts the lassign command. splits up the given list and assigns
   each element to one of the given variables. if there are more elements
   than variables, puts the remaining elements into the result.
*/
int Mkg_LassignCmd( ClientData pC, Tcl_Interp *pI, int objc, Tcl_Obj *CONST objv[] )
{
  int i, iObjc;
  Tcl_Obj **ppoObjv;

  if( objc < 2 )
    return _WNA( 1, "list ?varName ...?" );

  _LOGE( objv[1], &iObjc, &ppoObjv );

  for( i = 0; i < iObjc; i++ )
  {
    if( i+2 >= objc ) break;
    _OSV( objv[i+2], NULL, ppoObjv[i] );
  }

  for( ; i < iObjc; i++ )
    _LOAE( _GOR, ppoObjv[i] );

  return TCL_OK;
}

/* Mkg_LevalCmd
   implements the leval command. splits up the given list and evaluates
   the given command for each element of the list. the result of each
   evaluation is appended to the result.
*/
int Mkg_LevalCmd( ClientData pC, Tcl_Interp *pI, int objc, Tcl_Obj *CONST objv[] )
{
  int i, iObjc;
  char *pcCmd;
  Tcl_Obj *poResult, **ppoObjv;

  if( objc != 3 )
    return _WNA( 1, "list command" );

  pcCmd = _GSO( objv[2] );
  poResult = _NSO( "" );

  _LOGE( objv[1], &iObjc, &ppoObjv );

  for( i = 0; i < iObjc; i++ )
  {
    try( Tcl_VarEval( pI, pcCmd, " ", _GSO( ppoObjv[i] ), NULL ), eError );
    try( _LOAE( poResult, _GOR ), eError );
  }

  _SOR( poResult );

  return TCL_OK;

  catch eError:
    _DRC( poResult );
    return TCL_ERROR;
}

/* Mkg_LstatCmd
   implements the lstat command. checks if the specified option is valid
   and detects the -force option. sets start values depending on the operation.
   loops thru the list and calculates either minimum, maximum or sum,
   hereby detecting non-numeric arguments and keeping track of their count.
   if option count was specified, the number of numberic elements is returned,
   for min, max, sum and avg the result is returned, or nothing, if none of the elements
   was numeric. for option count the number of numeric elements is returned.
*/
int Mkg_LstatCmd( ClientData pC, Tcl_Interp *pI, int objc, Tcl_Obj *CONST objv[] )
{
  int      i, bForce, iValues, iMatch, iObjc;
  double   fRes, fValue;
  Tcl_Obj *poList, **ppoObjv;

  char *ppcOptions[] = { "count", "min", "max", "sum", "avg", NULL };

  if( objc < 3 || objc > 4 || ( objc == 4 && strcmp( _GSO( objv[2] ), "-force" ) ) )
    return _WNA( 1, "option ?-force? list" );

  try( _GIFO( objv[1], ppcOptions, "option", &iMatch ), eError );

  bForce = (objc == 4);
  poList = (objc == 3)? objv[2]:objv[3];

  _LOGE( poList, &iObjc, &ppoObjv );
  iValues = iObjc;

  switch( iMatch )
  {
    case 1 : fRes =  1.7E308; break;
    case 2 : fRes = -1.7E308; break;
    default: fRes =  0;
  }

  for( i = 0; i < iObjc; i++ )
  {
    if( _GDO( ppoObjv[i], &fValue ) == TCL_ERROR )
    {
      if( bForce )
      {
        iValues--;
        continue;
      }
      else
        throw eError;
    }

    switch( iMatch )
    {
      case 0 : break;
      case 1 : fRes  = (fValue < fRes)? fValue:fRes; break;
      case 2 : fRes  = (fValue > fRes)? fValue:fRes; break;
      default: fRes += fValue;
    }
  }

  if( iMatch == 0 )
    _SOR( _NIO( iValues ) );
  else if( iValues == 0 )
    _ROR;
  else if( iMatch == 4 )
    _SOR( _NDO( fRes/iValues ) );
  else
    _SOR( _NDO( fRes ) );

  return TCL_OK;

  catch eError:
    return TCL_ERROR;
}

/* Mkg_LunionCmd
   implements the lunion command. all given lists are concatenated
   to a single list, which is sorted by means of qsort. the sorted list
   elements are then appended to the result in the following way:
   if an element is not of the same value as its predecessor,
   it is appended to the result.
*/
int Mkg_LunionCmd( ClientData pC, Tcl_Interp *pI, int objc, Tcl_Obj *CONST objv[] )
{
  int i, iObjc;
  Tcl_Obj **ppoObjv, *poList;

  if( objc < 2 )
    return _WNA( 1, "list ?list ...?" );

  poList = Tcl_ConcatObj( objc-1, objv+1 );
  try( _LOGE( poList, &iObjc, &ppoObjv ), eError );

  if( iObjc )
  {
    qsort( ppoObjv, iObjc, sizeof(Tcl_Obj*), _MkgCompareObj );

    _LOAE( _GOR, ppoObjv[0] );
    for( i = 1; i < iObjc; i++ )
      if( strcmp( _GSO( ppoObjv[i-1] ), _GSO( ppoObjv[i] ) ) )
        _LOAE( _GOR, ppoObjv[i] );
  }

  _DRC( poList );

  return TCL_OK;

  catch eError:
    return TCL_ERROR;
}

/* Mkg_LinterCmd
   implements the linter and lminus commands. (int)pC is 1 for linter,
   and 0 for lminus. all given lists are sorted and processed pairwise:
   two counters run through the two lists, each trying to catch up with
   the counter of the other list. in case of linter, each value contained
   in both lists is added to the result. in case of lminus, values only
   contained in list1 are added to the result, and at the end all remainders
   of list1 are added, too. the result list is processed in the same
   manner with the next list, if any, or returned.
*/
int Mkg_LinterCmd( ClientData pC, Tcl_Interp *pI, int objc, Tcl_Obj *CONST objv[] )
{
  int bInter, i, iCmp, iOc1, iOc2, iL1, iL2;
  Tcl_Obj **ppoOv1, **ppoOv2, *poL1, *poL2;

  if( objc < 2 )
    return _WNA( 1, "list ?list ...?" );

  bInter = (int)pC;

  poL1 = _DOB( objv[1] ); _IRC( poL1 );
  try( _LOGE( poL1, &iOc1, &ppoOv1 ), eError );
  qsort( ppoOv1, iOc1, sizeof(Tcl_Obj*), _MkgCompareObj );

  if( iOc1 )
  {
    _LOAE( _GOR, ppoOv1[0] );
    for( i = 1; i < iOc1; i++ )
      if( strcmp( _GSO( ppoOv1[i-1] ), _GSO( ppoOv1[i] ) ) )
        _LOAE( _GOR, ppoOv1[i] );
  }

  _DRC( poL1 );
  for( i = 2; i < objc; i++ )
  {
    poL1 = _GOR; _IRC( poL1 );
    _LOGE( poL1, &iOc1, &ppoOv1 );

    poL2 = _DOB( objv[i] ); _IRC( poL2 );
    try( _LOGE( poL2, &iOc2, &ppoOv2 ), eError );
    qsort( ppoOv2, iOc2, sizeof(Tcl_Obj*), _MkgCompareObj );

    Tcl_ResetResult( pI );
    for( iL1 = 0, iL2 = 0; iL1 < iOc1 && iL2 < iOc2; )
    {
      iCmp = strcmp( _GSO( ppoOv1[iL1] ), _GSO( ppoOv2[iL2] ) );

      if( ! iCmp )
      {
        if( bInter ) _LOAE( _GOR, ppoOv1[iL1] );
        iL1++;
        iL2++;
      }
      else if( iCmp < 0 )
      {
        if( ! bInter )
          _LOAE( _GOR, ppoOv1[iL1] );

        while( ++iL1 < iOc1 && strcmp( _GSO( ppoOv1[iL1] ), _GSO( ppoOv2[iL2] ) ) < 0 )
          if( ! bInter ) _LOAE( _GOR, ppoOv1[iL1] );
      }
      else
        while( ++iL2 < iOc2 && strcmp( _GSO( ppoOv1[iL1] ), _GSO( ppoOv2[iL2] ) ) > 0 );
    }

    if( ! bInter )
      for( ; iL1 < iOc1; iL1++ )
        _LOAE( _GOR, ppoOv1[iL1] );

    _DRC( poL1 );
    _DRC( poL2 );
  }

  return TCL_OK;

  catch eError:
    return TCL_ERROR;
}

/* Mkg_LmirrorCmd
   implements the lmirror command. the given list is split into elements.
   the elements are appended to a new list, starting with the last element.
*/
int Mkg_LmirrorCmd( ClientData pC, Tcl_Interp *pI, int objc, Tcl_Obj *CONST objv[] )
{
  int i, iObjc;
  Tcl_Obj *poList, **ppoObjv;

  if( objc != 2 )
    return _WNA( 1, "list" );

  poList = _NOB;

  _LOGE( objv[1], &iObjc, &ppoObjv );
  for( i = iObjc-1; i>= 0; i-- )
    _LOAE( poList, ppoObjv[i] );

  _SOR( poList );

  return TCL_OK;
}

/* Mkg_LoopCmd
   implements the loop command. if an increment is given and its first
   character is '?' then later the increment is automatically adjusted.
   otherwise, the increment is set to 1 resp. copied from objv[4].
   if lower or upper bound or the increment are not of type integer,
   the loop variable will later be set as a double, otherwise as integer.
   if the increment is zero, an infinite loop is indicated. otherwise,
   if the increment has the wrong sign, it is either adjusted or the
   function returns, depending on the '?' in objv[4] earlier.
   then, the number of iterations is calculated and a loop is processed.
   inside the loop, the loop variable is set, either as int or as double
   (see above), and the body is evaluated. if an evaluation returns
   an exception, then it is handled accordingly.
*/
int Mkg_LoopCmd( ClientData pC, Tcl_Interp *pI, int objc, Tcl_Obj *CONST objv[] )
{
  int i, iTest, iRes, iCount, bAuto, bDouble;
  double f, fFirst, fLast, fIncr;
  Tcl_Obj *poBody, *poIncr;

  if( objc < 5 || objc > 6 )
    return _WNA( 1, "varName first last ?step? body" );

  bAuto = ( objc == 6 && *_GSO( objv[4] ) == '?' );

  if( bAuto )
    poIncr = _NSO( _GSO( objv[4] ) + 1 );
  else if( objc == 6 )
    poIncr = _DOB( objv[4] );
  else
    poIncr = _NIO( 1 );

  bDouble = ( _GIO( objv[2], &iTest ) == TCL_ERROR ||
              _GIO( objv[3], &iTest ) == TCL_ERROR ||
              _GIO( poIncr , &iTest ) == TCL_ERROR );

  try( _GDO( objv[2], &fFirst ), eError );
  try( _GDO( objv[3], &fLast  ), eError );
  try( _GDO( poIncr , &fIncr  ), eError );

  _DRC( poIncr );
  _ROR;

  if( fIncr == 0 )
    throw eInfiniteLoop;
  else if( ( fLast < fFirst && fIncr > 0 ) || ( fLast > fFirst && fIncr < 0 ) )
  {
    if( bAuto )
      fIncr = -fIncr;
    else
      return TCL_OK;
  }

  poBody = objv[objc-1];
  iCount = (int)(fabs((fLast-fFirst)/fIncr)+1);
  for( f = fFirst, i = 0; i < iCount; i++ )
  {
    _OSV( objv[1], NULL, (bDouble)? _NDO( f ): _NIO( (int)f ) );

    iRes = Tcl_EvalObj( pI, poBody );
    f += fIncr;

    if     ( iRes == TCL_CONTINUE ) continue;
    else if( iRes == TCL_BREAK    ) break;
    else if( iRes != TCL_OK       ) throw eOthers;
  }

  return TCL_OK;

  catch eError:
    return TCL_ERROR;
  catch eOthers:
    return iRes;
  catch eInfiniteLoop:
    _SSO( _GOR, "Infinite loop" );
    return TCL_ERROR;
}

/* Mkg_DoCmd
   implements the do-while command. identical to the C/C++ do-while.
   body is evaluated at least once, then expr. if expr is false, the
   loop is exited.
*/
int Mkg_DoCmd( ClientData pC, Tcl_Interp *pI, int objc, Tcl_Obj *CONST objv[] )
{
  int    iRes, bExpr;

  if( objc != 4 || strcmp( _GSO( objv[2] ), "while" ) )
    return _WNA( 1, "body while expr" );

  do
  {
    iRes = Tcl_EvalObj( pI, objv[1] );

    if     ( iRes == TCL_CONTINUE ) continue;
    else if( iRes == TCL_BREAK    ) break;
    else if( iRes != TCL_OK       ) throw eOthers;

    try( Tcl_ExprBooleanObj( pI, objv[3], &bExpr ), eError );
  }
  while( bExpr );

  return TCL_OK;

  catch eError:
    return TCL_ERROR;
  catch eOthers:
    return iRes;
}

/* Mkg_FincrCmd
   implements the fincr command. works exactly like the incr commmand,
   just for floating point values.
*/
int Mkg_FincrCmd( ClientData pC, Tcl_Interp *pI, int objc, Tcl_Obj *CONST objv[] )
{
  double   fValue, fIncr;
  Tcl_Obj *poValue;

  if( objc < 2 || objc > 3 )
    return _WNA( 1, "varName ?value?" );

  if( objc == 2 )
    fIncr = 1.;
  else
    try( _GDO( objv[2], &fIncr ), eError );

  poValue = _OGV( objv[1], NULL );
  if( poValue == NULL )
    throw eError;

  try( _GDO( poValue, &fValue ), eError );

  poValue = _SOB( poValue );
  _SDO( poValue, fValue+fIncr );

  poValue = _OSV( objv[1], NULL, poValue );
  if( poValue == NULL )
    throw eError;

  _SOR( poValue );

  return TCL_OK;

  catch eError:
    return TCL_ERROR;
}

/* Mkg_OptionsCmd
   implements the options command. splits up the given list into its
   elements and unsets the array given by varName. allocates space
   for a char* array to hold pointers to the string representations
   of all given options. in the first loop this array is initialized
   and any default values are set. in the second loop each element is
   tested if it starts with an '-' and is not "--". If false, then there
   are no more options. the rest might be arguments, which are only
   allowed if -nocomplain or -allowargs was set. In that case, these
   arguments are appended to the result. If true, it must be an option,
   which is checked against the allowed options by means of the char* array.
   if the option is not found and -nocomplain was not specified, an error
   is returned, otherwise the unknown option is appended to the result.
   if the option is found, the option data is analyzed: if there is no
   extra data to the option, it is assumed to be an option without argument,
   and the corresponding value in the array is set to "1". if there is extra
   data to the option, it is assumed to be an option with an argument.
   if the 2nd element of the spec is "*" the corresponding array value is
   set accordingly. otherwise the 2nd element is treated as a list.
   another char* array is created and the value is searched in there, prior
   to setting the array value. if not found, an error message is produced.
*/
int Mkg_OptionsCmd( ClientData pC, Tcl_Interp *pI, int objc, Tcl_Obj *CONST objv[] )
{
  int i, j, iLc, iOc, iVc, iFirst, iCount, bNocom, bAllow;
  char **ppcOpts, **ppcArgs;
  Tcl_Obj *poList, *poRest, **ppoVv, **ppoLv, **ppoOv, *poAvar;

  if( objc < 4 )
    return _WNA( 1, "?-nocomplain|-allowargs? varName list optionSpec ?optionSpec ...?" );

  bNocom = ( strcmp( "-nocomplain", _GSO( objv[1] ) ) == 0 );
  bAllow = ( strcmp( "-allowargs", _GSO( objv[1] ) ) == 0 );

  iFirst = ( bNocom || bAllow )? 4:3;
  iCount = objc-iFirst;
  poList = objv[iFirst-1];
  poAvar = objv[iFirst-2];
  poRest = _NOB;

  _LOGE( poList, &iLc, &ppoLv );
  _OUV( poAvar, NULL );
  _ROR;

  ppcOpts = (char**)ckalloc( ( iCount + 1 ) * sizeof(char*) );
  ppcOpts[iCount] = NULL;

  for( i = 0; i < iCount; i++ )
  {
    _LOGE( objv[iFirst+i], &iOc, &ppoOv );
    if( iOc < 1 || iOc > 3 )
      throw eBadSpec;

    ppcOpts[i] = _GSO( ppoOv[0] );
    if( iOc == 3 )
      _OSV( poAvar, ppoOv[0], ppoOv[2] );
  }

  for( i = 0; i < iLc; i++ )
  {
    if( *( _GSO( ppoLv[i] ) ) != '-' && bAllow )
    {
      for( j = i; j < iLc; _LOAE( poRest, ppoLv[j++] ) );
      break;
    }
    else if( ! strcmp( _GSO( ppoLv[i] ), "--" ) && ( bAllow || bNocom ) )
    {
      for( j = i+1; j < iLc; _LOAE( poRest, ppoLv[j++] ) );
      break;
    }
    else if( _GIFO( ppoLv[i], ppcOpts, "option", &j ) != TCL_OK )
    {
      if( ! bNocom ) throw eError;
      _LOAE( poRest, ppoLv[i] );
      continue;
    }

    _LOGE( objv[iFirst+j], &iOc, &ppoOv );

    if( iOc == 1 )
      _OSV( poAvar, ppoOv[0], _NSO( "1" ) );
    else if( ++i == iLc )
      throw eMissingArg;
    else if( ! strcmp( _GSO( ppoOv[1] ), "*" ) )
      _OSV( poAvar, ppoOv[0], ppoLv[i] );
    else
    {
      _LOGE( ppoOv[1], &iVc, &ppoVv );
      ppcArgs = (char**)ckalloc( (iVc+1) * sizeof(char*) );
      ppcArgs[iVc] = NULL;

      for( j = 0; j < iVc; j++ )
        ppcArgs[j] = _GSO( ppoVv[j] );

      _ROR;
      try( _GIFO( ppoLv[i], ppcArgs, "value", &j ), eBadValue );
      _OSV( poAvar, ppoOv[0], ppoVv[j] );
      ckfree( (char*)ppcArgs );
    }
  }

  ckfree( (char*)ppcOpts );
  _SOR( poRest );

  return TCL_OK;

  catch eBadSpec:
    _SSO( _GOR, "At least one option spec is malformed" );
    ckfree( (char*)ppcOpts );
    return TCL_ERROR;
  catch eBadValue:
    ckfree( (char*)ppcArgs );
    throw eError;
  catch eMissingArg:
    _SSO( _GOR, "Missing argument for last option" );
    throw eError;
  catch eError:
    ckfree( (char*)ppcOpts );
    _DRC( poRest );
    return TCL_ERROR;
}

/* Mkg_HexdumpCmd
   implements the hexdump command. A string is read in chunks of 16 bytes
   and formatted as a typical hex dump. Each such formatted line is an
   element of the returned list.
*/
int Mkg_HexdumpCmd( ClientData pC, Tcl_Interp *pI, int objc, Tcl_Obj *CONST objv[] )
{
  int      i, iLen;
  char    *pcData, pcLine[200], pcBuf[50];
  Tcl_Obj *poList;

  if( objc != 2 )
    return _WNA( 1, "string" );

  pcData = _GAO( objv[1], &iLen );
  poList = _NOB;

  if( iLen )
  {
    for( i = 0; i < iLen; i++ )
    {
      if( ! (i%16) )
      {
        if( i )
          _LOAE( poList, _NSO( pcLine ) );

        sprintf( pcLine, "%06x  %65s", i, "" );
      }

      sprintf( pcBuf, "%02x", (unsigned char)pcData[i] );
      memcpy( pcLine+8+(i%16)*3, pcBuf, 2 );
      sprintf( pcBuf, "%c", (pcData[i] >= ' ' && pcData[i] <= '~' )? pcData[i]:'.' );
      memcpy( pcLine+57+(i%16), pcBuf, 1 );
    }

    _LOAE( poList, _NSO( pcLine ) );
  }

  _SOR( poList );

  return TCL_OK;
}

/* Mkg_EncryptCmd
   implements the encrypt command. the algorithm used is TEA, which encodes
   64 bits (2 longs) with a 128 bit key (4 longs). hence, the boundary for
   data to be encoded is 8 bytes. memory is allocated and the data is copied
   into it, then run through TEA in blocks of 8 bytes. the allocated memory
   is directly encrypted, so that in the end it holds the entiry encrypted
   data string. because of the 8 byte blocks, the length of the original data
   string is also coded into the encrypted string, so that it can be restored
   after decryption.
*/
int Mkg_EncryptCmd( ClientData pC, Tcl_Interp *pI, int objc, Tcl_Obj *CONST objv[] )
{
  int   i, iLen, iLen8;
  char *pcData, *pcOut, pcKey[16];

  if( objc != 3 )
    return _WNA( 1, "key data" );

  pcData = _GAO( objv[1], &iLen );           /* key can be a binary string */
  if( iLen < 1 || iLen > 16 )
    throw eBadKey;

  memset( pcKey, 0xAA, 16 );                       /* set some bit pattern */
  memcpy( pcKey, pcData, iLen );           /* ...then copy the key into it */
  _MkgConvertEndian( pcKey, 16 );

  pcData = _GAO( objv[2], &iLen );          /* data can be a binary string */
  iLen8  = ((sizeof(int)+iLen-1)/8+1)*8;    /* get 8 byte boundary, but... */
  pcOut  = ckalloc( iLen8 );              /*  ...add space to store length */
  memset( pcOut, 0, iLen8 );
  *((int*)pcOut) = iLen;                        /* first store data length */
  memcpy( pcOut+sizeof(int), pcData, iLen );          /* ...then copy data */
  _MkgConvertEndian( pcOut+sizeof(int), iLen8-sizeof(int) );

  for( i = 0; i < iLen8; i+= 8 )      /* now run TEA for each 64 bit block */
    _MkgTEAencrypt( (unsigned long*)(pcOut+i), (unsigned long*)pcKey );

  _SOR( _NAO( pcOut, iLen8 ) );           /* set result and free up memory */
  ckfree( (char*)pcOut );

  return TCL_OK;

  catch eBadKey:
    _SSO( _GOR, "Key length must be between 1 and 16 bytes" );
    return TCL_ERROR;
}

/* Mkg_DecryptCmd
   implements the decrypt command. basically the same as its counterpart.
   the data length must be a multiple of 8 bytes as generated by
   Mkg_EncryptCmd.
*/
int Mkg_DecryptCmd( ClientData pC, Tcl_Interp *pI, int objc, Tcl_Obj *CONST objv[] )
{
  int   i, iLen, iLen8;
  char *pcData, *pcOut, pcKey[16];

  if( objc != 3 )
    return _WNA( 1, "key data" );

  pcData = _GAO( objv[1], &iLen );
  if( iLen < 1 || iLen > 16 )
    throw eBadKey;

  memset( pcKey, 0xAA, 16 );
  memcpy( pcKey, pcData, iLen );
  _MkgConvertEndian( pcKey, 16 );

  pcData = _GAO( objv[2], &iLen8 );
  if( iLen8 == 0 || iLen8 % 8 )
    throw eBadData;

  pcOut  = ckalloc( iLen8 );
  memcpy( pcOut, pcData, iLen8 );

  for( i = 0; i < iLen8; i+= 8 )
    _MkgTEAdecrypt( (unsigned long*)(pcOut+i), (unsigned long*)pcKey );

  _MkgConvertEndian( pcOut+sizeof(int), iLen8-sizeof(int) );
  _SOR( _NAO( pcOut+sizeof(int), *((int*)pcOut) ) );
  ckfree( (char*)pcOut );

  return TCL_OK;

  catch eBadKey:
    _SSO( _GOR, "Key length must be between 1 and 16 bytes" );
    return TCL_ERROR;
  catch eBadData:
    _SSO( _GOR, "Data length must be a non-zero integer multiple of 8" );
    return TCL_ERROR;
}

/* Mkg_ChecksumCmd
   implements the checksum command. CRC (cyclic redundancy check)
   schemes are among the most reliable means of error detection in use.
   this particular implementation is table based and therefore very fast.
   the code is taken from the Internet and public domain.
*/
int Mkg_ChecksumCmd( ClientData pC, Tcl_Interp *pI, int objc, Tcl_Obj *CONST objv[] )
{
  static unsigned short piTab16[256] = {
    0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7,
    0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef,
    0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6,
    0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de,
    0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485,
    0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d,
    0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4,
    0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc,
    0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823,
    0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b,
    0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12,
    0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a,
    0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41,
    0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49,
    0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70,
    0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78,
    0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f,
    0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067,
    0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e,
    0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256,
    0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d,
    0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
    0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c,
    0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634,
    0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab,
    0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3,
    0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a,
    0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92,
    0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9,
    0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1,
    0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8,
    0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0
  };

  static unsigned long plTab32[256] = {
    0x00000000L, 0x77073096L, 0xee0e612cL, 0x990951baL, 0x076dc419L, 0x706af48fL, 0xe963a535L, 0x9e6495a3L,
    0x0edb8832L, 0x79dcb8a4L, 0xe0d5e91eL, 0x97d2d988L, 0x09b64c2bL, 0x7eb17cbdL, 0xe7b82d07L, 0x90bf1d91L,
    0x1db71064L, 0x6ab020f2L, 0xf3b97148L, 0x84be41deL, 0x1adad47dL, 0x6ddde4ebL, 0xf4d4b551L, 0x83d385c7L,
    0x136c9856L, 0x646ba8c0L, 0xfd62f97aL, 0x8a65c9ecL, 0x14015c4fL, 0x63066cd9L, 0xfa0f3d63L, 0x8d080df5L,
    0x3b6e20c8L, 0x4c69105eL, 0xd56041e4L, 0xa2677172L, 0x3c03e4d1L, 0x4b04d447L, 0xd20d85fdL, 0xa50ab56bL,
    0x35b5a8faL, 0x42b2986cL, 0xdbbbc9d6L, 0xacbcf940L, 0x32d86ce3L, 0x45df5c75L, 0xdcd60dcfL, 0xabd13d59L,
    0x26d930acL, 0x51de003aL, 0xc8d75180L, 0xbfd06116L, 0x21b4f4b5L, 0x56b3c423L, 0xcfba9599L, 0xb8bda50fL,
    0x2802b89eL, 0x5f058808L, 0xc60cd9b2L, 0xb10be924L, 0x2f6f7c87L, 0x58684c11L, 0xc1611dabL, 0xb6662d3dL,
    0x76dc4190L, 0x01db7106L, 0x98d220bcL, 0xefd5102aL, 0x71b18589L, 0x06b6b51fL, 0x9fbfe4a5L, 0xe8b8d433L,
    0x7807c9a2L, 0x0f00f934L, 0x9609a88eL, 0xe10e9818L, 0x7f6a0dbbL, 0x086d3d2dL, 0x91646c97L, 0xe6635c01L,
    0x6b6b51f4L, 0x1c6c6162L, 0x856530d8L, 0xf262004eL, 0x6c0695edL, 0x1b01a57bL, 0x8208f4c1L, 0xf50fc457L,
    0x65b0d9c6L, 0x12b7e950L, 0x8bbeb8eaL, 0xfcb9887cL, 0x62dd1ddfL, 0x15da2d49L, 0x8cd37cf3L, 0xfbd44c65L,
    0x4db26158L, 0x3ab551ceL, 0xa3bc0074L, 0xd4bb30e2L, 0x4adfa541L, 0x3dd895d7L, 0xa4d1c46dL, 0xd3d6f4fbL,
    0x4369e96aL, 0x346ed9fcL, 0xad678846L, 0xda60b8d0L, 0x44042d73L, 0x33031de5L, 0xaa0a4c5fL, 0xdd0d7cc9L,
    0x5005713cL, 0x270241aaL, 0xbe0b1010L, 0xc90c2086L, 0x5768b525L, 0x206f85b3L, 0xb966d409L, 0xce61e49fL,
    0x5edef90eL, 0x29d9c998L, 0xb0d09822L, 0xc7d7a8b4L, 0x59b33d17L, 0x2eb40d81L, 0xb7bd5c3bL, 0xc0ba6cadL,
    0xedb88320L, 0x9abfb3b6L, 0x03b6e20cL, 0x74b1d29aL, 0xead54739L, 0x9dd277afL, 0x04db2615L, 0x73dc1683L,
    0xe3630b12L, 0x94643b84L, 0x0d6d6a3eL, 0x7a6a5aa8L, 0xe40ecf0bL, 0x9309ff9dL, 0x0a00ae27L, 0x7d079eb1L,
    0xf00f9344L, 0x8708a3d2L, 0x1e01f268L, 0x6906c2feL, 0xf762575dL, 0x806567cbL, 0x196c3671L, 0x6e6b06e7L,
    0xfed41b76L, 0x89d32be0L, 0x10da7a5aL, 0x67dd4accL, 0xf9b9df6fL, 0x8ebeeff9L, 0x17b7be43L, 0x60b08ed5L,
    0xd6d6a3e8L, 0xa1d1937eL, 0x38d8c2c4L, 0x4fdff252L, 0xd1bb67f1L, 0xa6bc5767L, 0x3fb506ddL, 0x48b2364bL,
    0xd80d2bdaL, 0xaf0a1b4cL, 0x36034af6L, 0x41047a60L, 0xdf60efc3L, 0xa867df55L, 0x316e8eefL, 0x4669be79L,
    0xcb61b38cL, 0xbc66831aL, 0x256fd2a0L, 0x5268e236L, 0xcc0c7795L, 0xbb0b4703L, 0x220216b9L, 0x5505262fL,
    0xc5ba3bbeL, 0xb2bd0b28L, 0x2bb45a92L, 0x5cb36a04L, 0xc2d7ffa7L, 0xb5d0cf31L, 0x2cd99e8bL, 0x5bdeae1dL,
    0x9b64c2b0L, 0xec63f226L, 0x756aa39cL, 0x026d930aL, 0x9c0906a9L, 0xeb0e363fL, 0x72076785L, 0x05005713L,
    0x95bf4a82L, 0xe2b87a14L, 0x7bb12baeL, 0x0cb61b38L, 0x92d28e9bL, 0xe5d5be0dL, 0x7cdcefb7L, 0x0bdbdf21L,
    0x86d3d2d4L, 0xf1d4e242L, 0x68ddb3f8L, 0x1fda836eL, 0x81be16cdL, 0xf6b9265bL, 0x6fb077e1L, 0x18b74777L,
    0x88085ae6L, 0xff0f6a70L, 0x66063bcaL, 0x11010b5cL, 0x8f659effL, 0xf862ae69L, 0x616bffd3L, 0x166ccf45L,
    0xa00ae278L, 0xd70dd2eeL, 0x4e048354L, 0x3903b3c2L, 0xa7672661L, 0xd06016f7L, 0x4969474dL, 0x3e6e77dbL,
    0xaed16a4aL, 0xd9d65adcL, 0x40df0b66L, 0x37d83bf0L, 0xa9bcae53L, 0xdebb9ec5L, 0x47b2cf7fL, 0x30b5ffe9L,
    0xbdbdf21cL, 0xcabac28aL, 0x53b39330L, 0x24b4a3a6L, 0xbad03605L, 0xcdd70693L, 0x54de5729L, 0x23d967bfL,
    0xb3667a2eL, 0xc4614ab8L, 0x5d681b02L, 0x2a6f2b94L, 0xb40bbe37L, 0xc30c8ea1L, 0x5a05df1bL, 0x2d02ef8dL
  };

  int   i, iLen, iSize;
  char *pcData;
  unsigned short iCRC16;
  unsigned long  lCRC32;

  if( objc != 2 && objc != 4 )
    return _WNA( 1, "?-size 16|32? data" );

  if( objc == 4 )                                /* get the size, if given */
  {
    if( strcmp( _GSO( objv[1] ), "-size" ) )
      throw eBadOption;

    try( _GIO( objv[2], &iSize ), eError );

    if( iSize != 16 && iSize != 32 )                   /* must be 16 or 32 */
      throw eBadSize;
  }
  else
   iSize = 16;                                            /* default is 16 */

  pcData = _GAO( objv[objc-1], &iLen );

  if( iSize == 16 )
  {
    iCRC16 = 0;

    for( i = 1; i < iLen; i++ )
      iCRC16 = piTab16[(((iCRC16)>> 8) & 255)] ^ (iCRC16 << 8) ^ pcData[i];

    _SOR( _NIO( iCRC16 ) );
  }
  else
  {
    lCRC32 = 0xffffffffL;

    for( ; iLen >= 8; iLen -= 8 )
      for( i = 8; i; i-- )
        lCRC32 = plTab32[( (int)lCRC32 ^ (*pcData++) ) & 0xff] ^ ( lCRC32 >> 8 );

    while( iLen-- )
      lCRC32 = plTab32[( (int)lCRC32 ^ (*pcData++) ) & 0xff] ^ ( lCRC32 >> 8 );

    _SOR( _NIO( lCRC32 ^ 0xffffffffL ) );
  }

  return TCL_OK;

  catch eError:
    return TCL_ERROR;
  catch eBadOption:
    _SSO( _GOR, "Unknown option: Must be -size." );
    return TCL_ERROR;
  catch eBadSize:
    _SSO( _GOR, "Bad size value: Must be 16 or 32" );
    return TCL_ERROR;
}

/* Mkg_HashCmd
   implements the hash command. this here is SHA-256, a one-way hash algorithm
   (see http://csrc.nist.gov/cryptval/shs.html) that produces a 256-bit
   (32 bytes) key upon any given input data. the security against collision
   attacks is 128 bits. such algorithms are used for authentication, but not
   to create secrecy. there is lots of information about SHA-256 in the
   Internet, including example code. the code is tested on Win, Linux (both
   on intel platform, little endian) and HP-UX (big endian).
   to test, do a "binary scan [hash abc] h* key; puts $key". it should return
   ab8761fbf810fcae141404edd5ea22320b30163a6971a7c94b01ff162f0051da.
*/
int Mkg_HashCmd( ClientData pC, Tcl_Interp *pI, int objc, Tcl_Obj *CONST objv[] )
{
  int   iLen;
  char *pcData, pcResult[32];

  if( objc != 2 )
    return _WNA( 1, "data" );

  pcData = _GAO( objv[1], &iLen );
  _MkgSha256( pcData, iLen, pcResult );
  _SOR( _NAO( pcResult, 32 ) );

  return TCL_OK;
}

/* Mkg_IgnoreCmd
   implements the /# command. does nothing.
*/
int Mkg_IgnoreCmd( ClientData pC, Tcl_Interp *pI, int objc, Tcl_Obj *CONST objv[] )
{
  return TCL_OK;
}

/* Mkgeneric_Init
   package initialization. creates all new commands and math functions
   and registers the package.
*/
int Mkgeneric_Init( Tcl_Interp *pI )
{
  Tcl_ValueType piT[] = { TCL_EITHER, TCL_EITHER };

  if( TCL_MAJOR_VERSION < 8 )
    throw eWrongVersion;

#ifdef USE_TCL_STUBS
  if( Tcl_InitStubs( pI, "8.3", 0) == NULL )
    throw eError;
#endif

  Tcl_CreateMathFunc( pI, "round2"  , 2, piT, Mkg_Round2Func, (ClientData)0 );
  Tcl_CreateMathFunc( pI, "floor2"  , 2, piT, Mkg_Round2Func, (ClientData)1 );
  Tcl_CreateMathFunc( pI, "ceil2"   , 2, piT, Mkg_Round2Func, (ClientData)2 );
  Tcl_CreateMathFunc( pI, "min"     , 2, piT, Mkg_MinMaxFunc, (ClientData)0 );
  Tcl_CreateMathFunc( pI, "max"     , 2, piT, Mkg_MinMaxFunc, (ClientData)1 );
  Tcl_CreateMathFunc( pI, "isdouble", 1, piT, Mkg_IsTypeFunc, (ClientData)0 );
  Tcl_CreateMathFunc( pI, "isint"   , 1, piT, Mkg_IsTypeFunc, (ClientData)1 );
  Tcl_CreateMathFunc( pI, "isodd"   , 1, piT, Mkg_IsEvenFunc, (ClientData)0 );
  Tcl_CreateMathFunc( pI, "iseven"  , 1, piT, Mkg_IsEvenFunc, (ClientData)1 );
  Tcl_CreateMathFunc( pI, "pi"      , 0, piT, Mkg_ConstFunc,  (ClientData)0 );
  Tcl_CreateMathFunc( pI, "e"       , 0, piT, Mkg_ConstFunc,  (ClientData)1 );

  Tcl_CreateObjCommand( pI, "throw"   , Mkg_ThrowCmd   , NULL, NULL );
  Tcl_CreateObjCommand( pI, "try"     , Mkg_TryCmd     , NULL, NULL );
  Tcl_CreateObjCommand( pI, "complete", Mkg_CompleteCmd, NULL, NULL );
  Tcl_CreateObjCommand( pI, "decode"  , Mkg_DecodeCmd  , NULL, NULL );
  Tcl_CreateObjCommand( pI, "linlist" , Mkg_LinlistCmd , NULL, NULL );
  Tcl_CreateObjCommand( pI, "ldelete" , Mkg_LdeleteCmd , NULL, NULL );
  Tcl_CreateObjCommand( pI, "lextend" , Mkg_LextendCmd , NULL, NULL );
  Tcl_CreateObjCommand( pI, "lshrink" , Mkg_LshrinkCmd , NULL, NULL );
  Tcl_CreateObjCommand( pI, "lchange" , Mkg_LchangeCmd , NULL, NULL );
  Tcl_CreateObjCommand( pI, "lnext"   , Mkg_LnextCmd   , (ClientData)1, NULL );
  Tcl_CreateObjCommand( pI, "lprev"   , Mkg_LnextCmd   , (ClientData)0, NULL );
  Tcl_CreateObjCommand( pI, "lassign" , Mkg_LassignCmd , NULL, NULL );
  Tcl_CreateObjCommand( pI, "leval"   , Mkg_LevalCmd   , NULL, NULL );
  Tcl_CreateObjCommand( pI, "lstat"   , Mkg_LstatCmd   , NULL, NULL );
  Tcl_CreateObjCommand( pI, "lunion"  , Mkg_LunionCmd  , NULL, NULL );
  Tcl_CreateObjCommand( pI, "linter"  , Mkg_LinterCmd  , (ClientData)1, NULL );
  Tcl_CreateObjCommand( pI, "lminus"  , Mkg_LinterCmd  , (ClientData)0, NULL );
  Tcl_CreateObjCommand( pI, "lmirror" , Mkg_LmirrorCmd , NULL, NULL );
  Tcl_CreateObjCommand( pI, "loop"    , Mkg_LoopCmd    , NULL, NULL );
  Tcl_CreateObjCommand( pI, "do"      , Mkg_DoCmd      , NULL, NULL );
  Tcl_CreateObjCommand( pI, "fincr"   , Mkg_FincrCmd   , NULL, NULL );
  Tcl_CreateObjCommand( pI, "options" , Mkg_OptionsCmd , NULL, NULL );
  Tcl_CreateObjCommand( pI, "hexdump" , Mkg_HexdumpCmd , NULL, NULL );
  Tcl_CreateObjCommand( pI, "encrypt" , Mkg_EncryptCmd , NULL, NULL );
  Tcl_CreateObjCommand( pI, "decrypt" , Mkg_DecryptCmd , NULL, NULL );
  Tcl_CreateObjCommand( pI, "checksum", Mkg_ChecksumCmd, NULL, NULL );
  Tcl_CreateObjCommand( pI, "hash"    , Mkg_HashCmd    , NULL, NULL );
  Tcl_CreateObjCommand( pI, "/#"      , Mkg_IgnoreCmd  , NULL, NULL );

  try( Tcl_PkgProvide( pI, "mkGeneric", _VERSION ), eError );

  return TCL_OK;

  catch eWrongVersion:
    _SSO( _GOR, "Package mkGeneric requires Tcl Version 8" );
    return TCL_ERROR;
  catch eError:
    return TCL_ERROR;
}

int Mkgeneric_SafeInit( Tcl_Interp *pI )
{
  return Mkgeneric_SafeInit( pI );
}

/* static linking. uncomment the following two functions if you want
   to create a stand-alone shell instead of a dynamic library. */

#ifndef USE_TCL_STUBS

int main( int argc, char *argv[] )
{
  Tcl_Main( argc, argv, Tcl_AppInit );
  return 0;
}

int Tcl_AppInit( Tcl_Interp *pI )
{
  try( Tcl_Init( pI ), eError );
  try( Mkgeneric_Init( pI ), eError );

  return TCL_OK;

  catch eError:
    return TCL_ERROR;
}

#endif

/*
 * mkGeneric 1.3
 * -------------
 */
