/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/*
 * bltDataTableCmd.c --
 *
 * Copyright 2015 George A. Howlett. All rights reserved.  
 *
 *   Redistribution and use in source and binary forms, with or without
 *   modification, are permitted provided that the following conditions are
 *   met:
 *
 *   1) Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 *   2) Redistributions in binary form must reproduce the above copyright
 *      notice, this list of conditions and the following disclaimer in the
 *      documentation and/or other materials provided with the
 *      distribution.
 *   3) Neither the name of the authors nor the names of its contributors
 *      may be used to endorse or promote products derived from this
 *      software without specific prior written permission.
 *   4) Products derived from this software may not be called "BLT" nor may
 *      "BLT" appear in their names without specific prior written
 *      permission from the author.
 *
 *   THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED
 *   WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 *   MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 *   DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 *   LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 *   CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 *   SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
 *   BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 *   WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
 *   OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
 *   IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */

#define BUILD_BLT_TCL_PROCS 1
#include "bltInt.h"
#include "bltMath.h"
#include "tclIntDecls.h"

#ifdef HAVE_STDLIB_H
  #include <stdlib.h>
#endif /* HAVE_STDLIB_H */

#ifdef HAVE_STRING_H
  #include <string.h>
#endif /* HAVE_STRING_H */

#ifdef HAVE_SYS_STAT_H
  #include <sys/stat.h>
#endif  /* HAVE_SYS_STAT_H */

#ifdef HAVE_CTYPE_H
  #include <ctype.h>
#endif /* HAVE_CTYPE_H */

#include "tclIntDecls.h"

#include "bltMath.h"

#define TABLE_THREAD_KEY "BLT DataTable Command Interface"
#define TABLE_FIND_KEY "BLT DataTable Find Command Interface"

/*
 * TableCmdInterpData --
 *
 *      Structure containing global data, used on a interpreter by
 *      interpreter basis.
 *
 *      This structure holds the hash table of instances of datatable
 *      commands associated with a particular interpreter.
 */
typedef struct {
    Blt_HashTable instTable;            /* Tracks tables in use. */
    Tcl_Interp *interp;
    Blt_HashTable fmtTable;
    Blt_HashTable findTable;            /* Tracks temporary "find" search
                                         * information keyed by a specific
                                         * namespace. */
} TableCmdInterpData;

/*
 * Cmd --
 *
 *      Structure representing the TCL command used to manipulate the
 *      underlying table object.
 *
 *      This structure acts as a shell for a table object.  A table object
 *      maybe shared by more than one client.  This shell provides Tcl
 *      commands to access and change values as well as the structure of
 *      the table itself.  It manages the traces and notifier events that
 *      it creates, providing a TCL interface to those facilities. It also
 *      provides a user-selectable value for empty-cell values.
 */
typedef struct {
    Tcl_Interp *interp;                 /* Interpreter this command is
                                         * associated with. */
    BLT_TABLE table;                    /* Handle representing the client
                                         * table. */
    Tcl_Command cmdToken;               /* Token for TCL command
                                         * representing this table. */
    const char *emptyString;             /* String representing an empty
                                         * value in the table. */
    Blt_HashTable *tablePtr;            /* Pointer to hash table containing
                                         * a pointer to this structure.
                                         * Used to delete * this table
                                         * entry from the table. */
    Blt_HashEntry *hPtr;                /* Pointer to the hash table entry
                                         * for this table in the
                                         * interpreter specific hash
                                         * table. */
    int nextTraceId;                    /* Used to generate trace id
                                         * strings.  */
    Blt_HashTable traceTable;           /* Table of active traces. Maps
                                         * trace ids back to their
                                         * TraceInfo records. */
    int nextWatch;                      /* Used to generate watch name
                                         * strings. */
    Blt_HashTable watchTable;           /* Table of event handlers. Maps
                                         * watch names back to their
                                         * WatchInfo records. */
} Cmd;

typedef struct {
    const char *name;                   /* Name of format. */
    unsigned int flags;                 /*  */
    BLT_TABLE_IMPORT_PROC *importProc;
    BLT_TABLE_EXPORT_PROC *exportProc;
} DataFormat;

#define FMT_LOADED      (1<<0)          /* Format is loaded. */
#define FMT_STATIC      (1<<1)          /* Format is static. */

enum DataFormats {
    FMT_TXT,                            /* Comma separated value files */
    FMT_CSV,                            /* Comma separated value files r/w */
#ifdef HAVE_LIBMYSQL
    FMT_MYSQL,                          /* Mysql r/w */
#endif
#ifdef HAVE_LIBSQLITE
    FMT_SQLITE,                          /* Sqlite3 r/w */
#endif
#ifdef HAVE_LIBPQ
    FMT_POSTGRESQL,                     /* Postgres r/w. */
#endif
    FMT_TREE,                           /* BLT Tree object r/w. */
    FMT_VECTOR,                         /* BLT Vector object r/w. */
#ifdef HAVE_EXPAT
    FMT_XML,                            /* XML r/w*/
#endif
    NUMFMTS
};

static DataFormat dataFormats[] = {
    { "txt" },                          /* White space separated values */
    { "csv" },                          /* Comma separated values */
#ifdef HAVE_LIBMYSQL
    { "mysql" },                        /* mysql client library. */
#endif
#ifdef HAVE_LIBSQLITE
    { "sqlite" },                       /* sqlite3 library. */
#endif
#ifdef HAVE_LIBPQ
    { "psql" },                         /* postgres library. */
#endif
    { "tree" },                         /* BLT Tree object.*/
    { "vector" },                       /* BLT Vector object.*/
#ifdef HAVE_EXPAT
    { "xml" },                          /* XML */
#endif
};

/*
 * TraceInfo --
 *
 *      Structure containing information about a trace set from this
 *      command shell.
 *
 *      This auxillary structure houses data to be used for a callback to a
 *      TCL procedure when a table object trace fires.  It is stored in a
 *      hash table in the Dt_Cmd structure to keep track of traces issued
 *      by this shell.
 */
typedef struct {
    BLT_TABLE_TRACE trace;
    Cmd *cmdPtr;
    Blt_HashEntry *hPtr;
    Blt_HashTable *tablePtr;
    int type;
    Tcl_Obj *cmdObjPtr;
} TraceInfo;

/*
 * WatchInfo --
 *
 *      Structure containing information about a notifier set from this
 *      command shell.
 *
 *      This auxillary structure houses data to be used for a callback to a
 *      TCL procedure when a table object notify event fires.  It is stored
 *      in a hash table in the Cmd structure to keep track of notifiers
 *      issued by this shell.
 */
typedef struct {
    BLT_TABLE_NOTIFIER notifier;
    Cmd *cmdPtr;
    Blt_HashEntry *hPtr;
    Tcl_Obj *cmdObjPtr;
} WatchInfo;

BLT_EXTERN Blt_SwitchFreeProc blt_table_column_iter_free_proc;
BLT_EXTERN Blt_SwitchFreeProc blt_table_row_iter_free_proc;
BLT_EXTERN Blt_SwitchParseProc blt_table_column_iter_switch_proc;
BLT_EXTERN Blt_SwitchParseProc blt_table_row_iter_switch_proc;
static Blt_SwitchParseProc TableSwitchProc;
static Blt_SwitchFreeProc TableFreeProc;
static Blt_SwitchParseProc ColumnTypeSwitchProc;
static Blt_SwitchParseProc AfterColumnSwitch;
static Blt_SwitchParseProc AfterRowSwitch;

static Blt_SwitchCustom columnIterSwitch = {
    blt_table_column_iter_switch_proc, NULL, blt_table_column_iter_free_proc, 0,
};
static Blt_SwitchCustom rowIterSwitch = {
    blt_table_row_iter_switch_proc, NULL, blt_table_row_iter_free_proc, 0,
};
static Blt_SwitchCustom tableSwitch = {
    TableSwitchProc, NULL, TableFreeProc, 0,
};
static Blt_SwitchCustom columnTypeSwitch = {
    ColumnTypeSwitchProc, NULL, NULL, 0,
};

#ifdef notdef

static Blt_SwitchParseProc ColumnsSwitchProc;
static Blt_SwitchFreeProc ColumnsFreeProc;
static Blt_SwitchCustom columnsSwitch = {
    ColumnsSwitchProc, NULL, ColumnsFreeProc, 0,
};

static Blt_SwitchParseProc RowsSwitchProc;
static Blt_SwitchFreeProc RowsFreeProc;
static Blt_SwitchCustom rowsSwitch = {
    RowsSwitchProc, NULL, RowsFreeProc, 0,
};

#endif

#define INSERT_BEFORE   (0)
#define INSERT_AFTER    (1<<0)

static Blt_SwitchCustom afterRowSwitch = {
    AfterRowSwitch, NULL, NULL, 0
};
static Blt_SwitchCustom afterColumnSwitch = {
    AfterColumnSwitch, NULL, NULL, 0
};

typedef struct {
    unsigned int perm, type;            /* Indicate the permission and type
                                         * of directory entries to search
                                         * for.*/
    unsigned int mask;                  /* Indicates which fields to copy
                                         * into the table columns. */
    Tcl_Obj *patternsObjPtr;            /* If non-NULL, is a list of
                                         * patterns to match contents of
                                         * each directory.  */
    unsigned int flags;
} ReadDirectory;

/* Flags to indicate what output fields to create. */
#define READ_DIR_TYPE        (1<<0)
#define READ_DIR_MODE        (1<<1)
#define READ_DIR_SIZE        (1<<2)
#define READ_DIR_UID         (1<<3)
#define READ_DIR_GID         (1<<4)
#define READ_DIR_ATIME       (1<<5)
#define READ_DIR_CTIME       (1<<6)
#define READ_DIR_MTIME       (1<<7)
#define READ_DIR_INO         (1<<8)
#define READ_DIR_NLINK       (1<<9)
#define READ_DIR_DEV         (1<<10)
#define READ_DIR_PERMS       (1<<11)

#define READ_DIR_ALL         \
    (READ_DIR_ATIME|READ_DIR_CTIME|READ_DIR_MTIME|READ_DIR_UID|READ_DIR_GID|\
     READ_DIR_TYPE|READ_DIR_MODE|READ_DIR_SIZE|READ_DIR_INO|READ_DIR_NLINK|\
     READ_DIR_DEV|READ_DIR_PERMS)

#define READ_DIR_DEFAULT     \
    (READ_DIR_MTIME|READ_DIR_TYPE|READ_DIR_PERMS|READ_DIR_SIZE)

/* Various flags for read dir operation */
#define READ_DIR_RECURSE             (1<<11)
#define READ_DIR_NOCASE              (1<<12)
#define READ_DIR_IGNORE_HIDDEN_DIRS  (1<<13)

static Blt_SwitchParseProc FieldsSwitchProc;
static Blt_SwitchCustom fieldsSwitch = {
    FieldsSwitchProc, NULL, NULL, (ClientData)0,
};
static Blt_SwitchParseProc TypeSwitchProc;
static Blt_SwitchCustom typeSwitch = {
    TypeSwitchProc, NULL, NULL, (ClientData)0,
};

static Blt_SwitchParseProc PermSwitchProc;
static Blt_SwitchCustom permSwitch = {
    PermSwitchProc, NULL, NULL, (ClientData)0,
};

static Blt_SwitchSpec dirSwitches[] = 
{
    {BLT_SWITCH_CUSTOM,  "-fields",  "fieldList", (char *)NULL,
        Blt_Offset(ReadDirectory, mask),    0, 0, &fieldsSwitch},
    {BLT_SWITCH_BITS_NOARG, "-hidden", "", (char *)NULL,
        Blt_Offset(ReadDirectory, perm), 0, TCL_GLOB_PERM_HIDDEN},
#if (_TCL_VERSION > _VERSION(8,5,0)) 
    {BLT_SWITCH_BITS_NOARG, "-nocase",       "", (char *)NULL,
        Blt_Offset(ReadDirectory, flags), 0, READ_DIR_NOCASE},
#endif
    {BLT_SWITCH_OBJ,     "-patterns",     "list", (char *)NULL,
        Blt_Offset(ReadDirectory, patternsObjPtr), 0},
    {BLT_SWITCH_CUSTOM,  "-permissions", "permList", (char *)NULL,
        Blt_Offset(ReadDirectory, perm),    0, 0, &permSwitch},
    {BLT_SWITCH_BITS_NOARG, "-readonly", "", (char *)NULL,
        Blt_Offset(ReadDirectory, perm), 0, TCL_GLOB_PERM_RONLY},
    {BLT_SWITCH_CUSTOM,  "-type",    "typeList", (char *)NULL,
        Blt_Offset(ReadDirectory, type),    0, 0, &typeSwitch},
    {BLT_SWITCH_END}
};

typedef struct {
    Cmd *cmdPtr;
    BLT_TABLE_ROW destRow;              /* Index where to install new
                                         * row. */
    const char *label;                  /* New label. */
    Tcl_Obj *tags;                      /* List of tags to be applied to
                                         * this column. */
    unsigned int flags;
} InsertRowSwitches;

static Blt_SwitchSpec insertRowSwitches[] = 
{
    {BLT_SWITCH_CUSTOM, "-after",  "rowName",       (char *)NULL,
        Blt_Offset(InsertRowSwitches, destRow),    0, 0, &afterRowSwitch},
    {BLT_SWITCH_CUSTOM, "-before", "rowName",       (char *)NULL,
         Blt_Offset(InsertRowSwitches, destRow),   0, 0, &afterRowSwitch},
    {BLT_SWITCH_STRING, "-label",  "string",    (char *)NULL,
        Blt_Offset(InsertRowSwitches, label),  0},
    {BLT_SWITCH_OBJ,    "-tags",   "tagList",      (char *)NULL,
        Blt_Offset(InsertRowSwitches, tags),   0},
    {BLT_SWITCH_END}
};

typedef struct {
    Cmd *cmdPtr;
    BLT_TABLE_COLUMN destColumn;        /* Index where to install new
                                         * column. */
    const char *label;                  /* New label. */
    Tcl_Obj *tags;                      /* List of tags to be applied to
                                         * this row or column. */
    BLT_TABLE_COLUMN_TYPE type;
    unsigned int flags;
} InsertColumnSwitches;

static Blt_SwitchSpec insertColumnSwitches[] = 
{
    {BLT_SWITCH_CUSTOM, "-after",  "colName",    (char *)NULL,
        Blt_Offset(InsertColumnSwitches, destColumn), 0, 0, &afterColumnSwitch},
    {BLT_SWITCH_CUSTOM, "-before", "colName",    (char *)NULL,
        Blt_Offset(InsertColumnSwitches, destColumn), 0, 0, &afterColumnSwitch},
    {BLT_SWITCH_STRING, "-label",  "string",    (char *)NULL,
        Blt_Offset(InsertColumnSwitches, label),  0},
    {BLT_SWITCH_OBJ,    "-tags",   "tagList",      (char *)NULL,
        Blt_Offset(InsertColumnSwitches, tags),   0},
    {BLT_SWITCH_CUSTOM, "-type",   "columnType",      (char *)NULL,
        Blt_Offset(InsertColumnSwitches, type),   0, 0, &columnTypeSwitch},
    {BLT_SWITCH_END}
};

typedef struct {
    unsigned int flags;
} IndicesSwitches;

#define INDICES_DUPLICATES      (1<<0)

static Blt_SwitchSpec indicesSwitches[] = 
{
    {BLT_SWITCH_BITS_NOARG, "-duplicates",  "",    (char *)NULL,
        Blt_Offset(IndicesSwitches, flags), 0, INDICES_DUPLICATES},
    {BLT_SWITCH_END}
};

typedef struct {
    unsigned int flags;
    BLT_TABLE table;
} CopySwitches;

#define COPY_NOTAGS     (1<<1)
#define COPY_LABEL      (1<<3)
#define COPY_APPEND     (1<<3)
#define COPY_NEW        (1<<4)

static Blt_SwitchSpec copySwitches[] = 
{
    {BLT_SWITCH_BITS_NOARG, "-append", "", (char *)NULL,
        Blt_Offset(CopySwitches, flags), 0, COPY_APPEND},
    {BLT_SWITCH_BITS_NOARG, "-new", "", (char *)NULL,
        Blt_Offset(CopySwitches, flags), 0, COPY_NEW},
    {BLT_SWITCH_BITS_NOARG, "-notags", "", (char *)NULL,
        Blt_Offset(CopySwitches, flags), 0, COPY_NOTAGS},
    {BLT_SWITCH_CUSTOM, "-table", "srcTable", (char *)NULL,
        Blt_Offset(CopySwitches, table), 0, 0, &tableSwitch},
    {BLT_SWITCH_END}
};

typedef struct {
    const char **labels;
} ExtendSwitches;

static Blt_SwitchSpec extendSwitches[] = 
{
    {BLT_SWITCH_LIST, "-labels",  "list",    (char *)NULL,
        Blt_Offset(ExtendSwitches, labels), 0, 0},
    {BLT_SWITCH_END}
};


typedef struct {
    unsigned int flags;
    BLT_TABLE_ITERATOR ri, ci;
} JoinSwitches;

#define JOIN_ROW        (BLT_SWITCH_USER_BIT)
#define JOIN_COLUMN     (BLT_SWITCH_USER_BIT<<1)
#define JOIN_BOTH       (JOIN_ROW|JOIN_COLUMN)
#define JOIN_NOTAGS     (1<<1)

static Blt_SwitchSpec joinSwitches[] = 
{
    {BLT_SWITCH_CUSTOM, "-columns",   "columnList" ,(char *)NULL,
        Blt_Offset(JoinSwitches, ci),   JOIN_COLUMN, 0, &columnIterSwitch},
    {BLT_SWITCH_BITS_NOARG, "-notags", "", (char *)NULL,
        Blt_Offset(JoinSwitches, flags), JOIN_BOTH, JOIN_NOTAGS},
    {BLT_SWITCH_CUSTOM, "-rows",      "rowList", (char *)NULL,
        Blt_Offset(JoinSwitches, ri),   JOIN_ROW, 0, &rowIterSwitch},
    {BLT_SWITCH_END}
};

typedef struct {
    unsigned int flags;
    BLT_TABLE_ITERATOR ri, ci;
} AddSwitches;

#define ADD_NOTAGS      (1<<1)

static Blt_SwitchSpec addSwitches[] = 
{
    {BLT_SWITCH_BITS_NOARG, "-notags", "", (char *)NULL,
        Blt_Offset(AddSwitches, flags), 0, ADD_NOTAGS},
    {BLT_SWITCH_CUSTOM, "-columns",   "columns" ,(char *)NULL,
        Blt_Offset(AddSwitches, ci),   0, 0, &columnIterSwitch},
    {BLT_SWITCH_CUSTOM, "-rows",      "rows", (char *)NULL,
        Blt_Offset(AddSwitches, ri),   0, 0, &rowIterSwitch},
    {BLT_SWITCH_END}
};

typedef struct {
    unsigned int flags;
} MoveSwitches;

#define MOVE_AFTER     (1<<1)

static Blt_SwitchSpec moveSwitches[] = 
{
    {BLT_SWITCH_BITS_NOARG, "-after", "", (char *)NULL,
        Blt_Offset(CopySwitches, flags), 0, MOVE_AFTER},
    {BLT_SWITCH_END}
};

typedef struct {
    /* Private data */
    Tcl_Channel channel;
    Tcl_DString *dsPtr;
    unsigned int flags;

    /* Public fields */
    BLT_TABLE_ITERATOR ri, ci;
    Tcl_Obj *fileObjPtr;
} DumpSwitches;

static Blt_SwitchSpec dumpSwitches[] = 
{
    {BLT_SWITCH_CUSTOM, "-rows",    "rows", (char *)NULL,
        Blt_Offset(DumpSwitches, ri),      0, 0, &rowIterSwitch},
    {BLT_SWITCH_CUSTOM, "-columns", "columns", (char *)NULL,
        Blt_Offset(DumpSwitches, ci),      0, 0, &columnIterSwitch},
    {BLT_SWITCH_OBJ,    "-file",    "fileName", (char *)NULL,
        Blt_Offset(DumpSwitches, fileObjPtr), 0},
    {BLT_SWITCH_END}
};

typedef struct {
    Blt_HashTable idTable;

    Tcl_Obj *fileObjPtr;
    Tcl_Obj *dataObjPtr;
    unsigned int flags;
} RestoreSwitches;

static Blt_SwitchSpec restoreSwitches[] = 
{
    {BLT_SWITCH_OBJ, "-data", "string", (char *)NULL,
        Blt_Offset(RestoreSwitches, dataObjPtr), 0, 0},
    {BLT_SWITCH_OBJ, "-file", "fileName", (char *)NULL,
        Blt_Offset(RestoreSwitches, fileObjPtr), 0, 0},
    {BLT_SWITCH_BITS_NOARG, "-notags", "", (char *)NULL,
        Blt_Offset(RestoreSwitches, flags), 0, TABLE_RESTORE_NO_TAGS},
    {BLT_SWITCH_BITS_NOARG, "-overwrite", "", (char *)NULL,
        Blt_Offset(RestoreSwitches, flags), 0, TABLE_RESTORE_OVERWRITE},
    {BLT_SWITCH_END}
};

typedef struct {
    unsigned int flags;
    unsigned int returnType;
    unsigned int tsFlags;
    BLT_TABLE_ITERATOR ri, ci;
    Tcl_Obj *freqArrVarObjPtr;          /* Specifies TCL array variable to 
                                         * store value/frequency pairs. */
    Tcl_Obj *freqListVarObjPtr;         /* Specifies TCL variable to store
                                         * list of frequencies, one per
                                         * row. */
} SortSwitches;

#define SORT_UNIQUE     (1<<16)         /* Indicates to output only the
                                         * first of an equal run.  */
#define SORT_ALTER      (1<<19)         /* Indicates to rearrange the
                                         * table. */
#define SORT_NONEMPTY   (1<<20)         /* Indicates to not consider empty
                                         * values when sorting.  */
#define SORT_BYFREQ     (1<<21)         /* Indicates to sort by the
                                         * frequency of the value. */
#define SORT_RETURN_INDICES (0)         /* Indicates to return the sorted
                                         * rows by their labels. */
#define SORT_RETURN_VALUES  (1)         /* Indicates to return the sorted
                                         * rows by their values. */
#define SORT_RETURN_LABELS  (2)         /* Indicates to return the sorted
                                         * rows by their labels. */
static Blt_SwitchSpec sortSwitches[] = 
{
    {BLT_SWITCH_BITS_NOARG, "-alter", "", (char *)NULL,
        Blt_Offset(SortSwitches, flags), 0, SORT_ALTER},
    {BLT_SWITCH_BITS_NOARG, "-ascii",      "", (char *)NULL,
        Blt_Offset(SortSwitches, tsFlags),  0, TABLE_SORT_ASCII},
    {BLT_SWITCH_BITS_NOARG, "-byfrequency", "", (char *)NULL,
        Blt_Offset(SortSwitches, flags), 0, SORT_BYFREQ},
    {BLT_SWITCH_CUSTOM, "-columns", "", (char *)NULL,
        Blt_Offset(SortSwitches, ci), 0, 0, &columnIterSwitch},
    {BLT_SWITCH_BITS_NOARG, "-decreasing", "", (char *)NULL,
        Blt_Offset(SortSwitches, tsFlags), 0, TABLE_SORT_DECREASING},
    {BLT_SWITCH_BITS_NOARG, "-dictionary", "", (char *)NULL,
        Blt_Offset(SortSwitches, tsFlags), 0, TABLE_SORT_DICTIONARY},
    {BLT_SWITCH_OBJ, "-frequencyarrayvariable", "varName", (char *)NULL,
        Blt_Offset(SortSwitches, freqArrVarObjPtr)},
    {BLT_SWITCH_OBJ, "-frequencylistvariable", "varName", (char *)NULL,
        Blt_Offset(SortSwitches, freqListVarObjPtr)},
    {BLT_SWITCH_VALUE, "-indices", "", (char *)NULL,
        Blt_Offset(SortSwitches, returnType), 0, SORT_RETURN_INDICES},
    {BLT_SWITCH_VALUE, "-labels", "", (char *)NULL,
        Blt_Offset(SortSwitches, returnType), 0, SORT_RETURN_LABELS},
    {BLT_SWITCH_BITS_NOARG, "-nocase", "", (char *)NULL,
        Blt_Offset(SortSwitches, tsFlags), 0, TABLE_SORT_IGNORECASE},
    {BLT_SWITCH_BITS_NOARG, "-nonempty", "", (char *)NULL,
        Blt_Offset(SortSwitches, flags), 0, SORT_NONEMPTY},
    {BLT_SWITCH_CUSTOM, "-rows", "", (char *)NULL,
        Blt_Offset(SortSwitches, ri), 0, 0, &rowIterSwitch},
    {BLT_SWITCH_BITS_NOARG, "-unique", "", (char *)NULL,
        Blt_Offset(SortSwitches, flags), 0, SORT_UNIQUE},
    {BLT_SWITCH_VALUE, "-values", "", (char *)NULL,
        Blt_Offset(SortSwitches, returnType), 0, SORT_RETURN_VALUES},
    {BLT_SWITCH_END}
};

typedef struct {
    BLT_TABLE_ROW row;
    size_t count;
} FreqMap;

typedef struct {
    unsigned int flags;
} WatchSwitches;

static Blt_SwitchSpec watchSwitches[] = 
{
    {BLT_SWITCH_BITS_NOARG, "-allevents", "", (char *)NULL,
        Blt_Offset(WatchSwitches, flags), 0, TABLE_NOTIFY_ALL_EVENTS},
    {BLT_SWITCH_BITS_NOARG, "-create", "", (char *)NULL,
        Blt_Offset(WatchSwitches, flags), 0, TABLE_NOTIFY_CREATE},
    {BLT_SWITCH_BITS_NOARG, "-delete", "", (char *)NULL,
        Blt_Offset(WatchSwitches, flags), 0, TABLE_NOTIFY_DELETE},
    {BLT_SWITCH_BITS_NOARG, "-move", "",  (char *)NULL,
        Blt_Offset(WatchSwitches, flags), 0, TABLE_NOTIFY_MOVE},
    {BLT_SWITCH_BITS_NOARG, "-relabel", "", (char *)NULL,
        Blt_Offset(WatchSwitches, flags), 0, TABLE_NOTIFY_RELABEL},
    {BLT_SWITCH_BITS_NOARG, "-whenidle", "", (char *)NULL,
        Blt_Offset(WatchSwitches, flags), 0, TABLE_NOTIFY_WHENIDLE},
    {BLT_SWITCH_END}
};

typedef struct {
    BLT_TABLE table;                    /* Table to be evaluated */
    BLT_TABLE_ROW row;                  /* Current row. */
    Blt_HashTable varTable;             /* Variable cache. */
    BLT_TABLE_ITERATOR iter;
    Tcl_Namespace *nsPtr;               /* Old namespace. */

    /* Public values */
    Tcl_Obj *emptyValueObjPtr;
    Tcl_Obj *prefixObjPtr;
    const char *tag;
    unsigned int flags;
    size_t maxMatches;
} FindSwitches;

#define FIND_INVERT     (1<<0)

static Blt_SwitchSpec findSwitches[] = 
{
    {BLT_SWITCH_STRING, "-addtag", "tagName", (char *)NULL,
        Blt_Offset(FindSwitches, tag), 0},
    {BLT_SWITCH_OBJ,    "-emptyvalue", "string", (char *)NULL,
        Blt_Offset(FindSwitches, emptyValueObjPtr), 0},
    {BLT_SWITCH_BITS_NOARG, "-invert", "", (char *)NULL,
        Blt_Offset(FindSwitches, flags), 0, FIND_INVERT},
    {BLT_SWITCH_LONG_NNEG, "-maxrows", "numRows", (char *)NULL,
        Blt_Offset(FindSwitches, maxMatches), 0},
    {BLT_SWITCH_OBJ, "-prefix", "string", (char *)NULL,
        Blt_Offset(FindSwitches, prefixObjPtr), 0},
    {BLT_SWITCH_CUSTOM, "-rows", "rowList", (char *)NULL,
        Blt_Offset(FindSwitches, iter), 0, 0, &rowIterSwitch},
    {BLT_SWITCH_END}
};

static BLT_TABLE_TRACE_PROC TraceProc;
static BLT_TABLE_TRACE_DELETE_PROC TraceDeleteProc;

static BLT_TABLE_NOTIFY_EVENT_PROC NotifyProc;
static BLT_TABLE_NOTIFIER_DELETE_PROC NotifierDeleteProc;

static Tcl_CmdDeleteProc TableInstDeleteProc;
static Tcl_InterpDeleteProc TableInterpDeleteProc;
static Tcl_ObjCmdProc TableInstObjCmd;
static Tcl_ObjCmdProc TableObjCmd;

static int
LoadFormat(Tcl_Interp *interp, const char *name)
{
    Tcl_Obj *pkgObjPtr;
    const char *version, *pkg;

    pkgObjPtr = Tcl_NewStringObj("blt_datatable_", 14);
    Tcl_AppendToObj(pkgObjPtr, name, -1);
    Blt_LowerCase(Tcl_GetString(pkgObjPtr));
    pkg = Tcl_GetString(pkgObjPtr);
    version = Tcl_PkgRequire(interp, pkg, BLT_VERSION, PKG_EXACT);
    Tcl_DecrRefCount(pkgObjPtr);
    if (version == NULL) {
        Tcl_ResetResult(interp);
        return FALSE;
    }
    return TRUE;
}

static Tcl_Obj *
GetColumnIndexObj(BLT_TABLE table, BLT_TABLE_COLUMN col) 
{
    size_t index;

    index = blt_table_column_index(table, col);
    return Tcl_NewWideIntObj(index);
}

static Tcl_Obj *
GetRowIndexObj(BLT_TABLE table, BLT_TABLE_ROW row) 
{
    size_t index;

    index = blt_table_row_index(table, row);
    return Tcl_NewWideIntObj(index);
}

static Tcl_Obj *
GetRowLabelObj(BLT_TABLE table, BLT_TABLE_ROW row) 
{
    const char *string;

    string = blt_table_row_label(row);
    return Tcl_NewStringObj(string, -1);
}


/*
 *---------------------------------------------------------------------------
 *
 * FieldsSwitch --
 *
 *      Convert a string representing a list of field names into a mask.
 *
 * Results:
 *      The return value is a standard TCL result.
 *
 *---------------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
FieldsSwitchProc(
    ClientData clientData,              /* Not used. */
    Tcl_Interp *interp,                 /* Interpreter to send results back
                                         * to */
    const char *switchName,             /* Not used. */
    Tcl_Obj *objPtr,                    /* String representation */
    char *record,                       /* Structure record */
    int offset,                         /* Offset to field in structure */
    int flags)                          /* Not used. */
{
    int *maskPtr = (int *)(record + offset);
    Tcl_Obj **objv;
    int objc, i;
    unsigned int mask;

    if (Tcl_ListObjGetElements(interp, objPtr, &objc, &objv) != TCL_OK) {
        return TCL_ERROR;
    }
    mask = 0;
    for (i = 0; i < objc; i++) {
        const char *string;
        char c;

        string = Tcl_GetString(objv[i]);
        c = string[0];
        if ((c == 's') && (strcmp(string, "size") == 0)) {
            mask |= READ_DIR_SIZE;
        } else if ((c == 'm') && (strcmp(string, "mode") == 0)) {
            mask |= READ_DIR_MODE;
        } else if ((c == 'p') && (strcmp(string, "perms") == 0)) {
            mask |= READ_DIR_PERMS;
        } else if ((c == 't') && (strcmp(string, "type") == 0)) {
            mask |= READ_DIR_TYPE;
        } else if ((c == 'u') && (strcmp(string, "uid") == 0)) {
            mask |= READ_DIR_UID;
        } else if ((c == 'g') && (strcmp(string, "gid") == 0)) {
            mask |= READ_DIR_GID;
        } else if ((c == 'a') && (strcmp(string, "atime") == 0)) {
            mask |= READ_DIR_ATIME;
        } else if ((c == 'c') && (strcmp(string, "ctime") == 0)) {
            mask |= READ_DIR_CTIME;
        } else if ((c == 'm') && (strcmp(string, "mtime") == 0)) {
            mask |= READ_DIR_MTIME;
        } else if ((c == 'i') && (strcmp(string, "ino") == 0)) {
            mask |= READ_DIR_INO;
        } else if ((c == 'd') && (strcmp(string, "dev") == 0)) {
            mask |= READ_DIR_DEV;
        } else if ((c == 'n') && (strcmp(string, "nlink") == 0)) {
            mask |= READ_DIR_NLINK;
        } else if ((c == 'a') && (strcmp(string, "all") == 0)) {
            mask |= READ_DIR_ALL;
        } else {
            Tcl_AppendResult(interp, "unknown field name \"", string, "\"",
                (char *)NULL);
            return TCL_ERROR;
        }
    }
    if (mask == 0) {
        mask = READ_DIR_DEFAULT;
    }
    *maskPtr = mask;
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * TypeSwitch --
 *
 *      Convert a string representing a list of type into a mask.
 *
 * Results:
 *      The return value is a standard TCL result.
 *
 *---------------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
TypeSwitchProc(
    ClientData clientData,              /* Not used. */
    Tcl_Interp *interp,                 /* Interpreter to send results back
                                         * to */
    const char *switchName,             /* Not used. */
    Tcl_Obj *objPtr,                    /* String representation */
    char *record,                       /* Structure record */
    int offset,                         /* Offset to field in structure */
    int flags)                          /* Not used. */
{
    int *maskPtr = (int *)(record + offset);
    Tcl_Obj **objv;
    int objc, i;
    unsigned int mask;

    if (Tcl_ListObjGetElements(interp, objPtr, &objc, &objv) != TCL_OK) {
        return TCL_ERROR;
    }
    mask = 0;
    for (i = 0; i < objc; i++) {
        const char *string;
        char c;
        int length;
        
        string = Tcl_GetStringFromObj(objv[i], &length);
        c = string[0];
        if ((c == 'f') && (strncmp(string, "file", length) == 0)) {
            mask |= TCL_GLOB_TYPE_FILE;
        } else if ((c == 'd') && (strncmp(string, "directory", length) == 0)) {
            mask |= TCL_GLOB_TYPE_DIR;
        } else if ((c == 'l') && (strncmp(string, "link", length) == 0)) {
            mask |= TCL_GLOB_TYPE_LINK;
        } else if ((c == 'p') && (strncmp(string, "pipe", length) == 0)) {
            mask |= TCL_GLOB_TYPE_PIPE;
        } else if ((c == 's') && (strncmp(string, "socket", length) == 0)) {
            mask |= TCL_GLOB_TYPE_SOCK;
        } else if ((c == 'b') && (strncmp(string, "block", length) == 0)) {
            mask |= TCL_GLOB_TYPE_BLOCK;
        } else if ((c == 'c') && (strncmp(string, "character", length) == 0)) {
            mask |= TCL_GLOB_TYPE_CHAR;
        } else {
            Tcl_AppendResult(interp, "unknown type name \"", string, "\"",
                (char *)NULL);
            return TCL_ERROR;
        }
    }
    *maskPtr = mask;
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * PermSwitch --
 *
 *      Convert a string representing a list of permissions into a mask.
 *
 * Results:
 *      The return value is a standard TCL result.
 *
 *---------------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
PermSwitchProc(
    ClientData clientData,              /* Not used. */
    Tcl_Interp *interp,                 /* Interpreter to send results back
                                         * to */
    const char *switchName,             /* Not used. */
    Tcl_Obj *objPtr,                    /* String representation */
    char *record,                       /* Structure record */
    int offset,                         /* Offset to field in structure */
    int flags)                          /* Not used. */
{
    int *maskPtr = (int *)(record + offset);
    int i, length;
    unsigned int mask;
    const char *string;

    string = Tcl_GetStringFromObj(objPtr, &length);
    mask = 0;
    for (i = 0; i < length; i++) {
        char c;
        
        c = string[i];
        if (c == 'r') {
            mask |= TCL_GLOB_PERM_R;
        } else if (c == 'w') {
            mask |= TCL_GLOB_PERM_W;
        } else if (c == 'x') {
            mask |= TCL_GLOB_PERM_X;
        } else {
            Tcl_AppendResult(interp, "unknown permssions \"", string, "\"",
                (char *)NULL);
            return TCL_ERROR;
        }
    }
    *maskPtr = mask;
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * AfterRowSwitch --
 *
 *      Convert a Tcl_Obj representing an offset in the table.
 *
 * Results:
 *      The return value is a standard TCL result.
 *
 *---------------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
AfterRowSwitch(
    ClientData clientData,              /* Flag indicating if the node is
                                         * considered before or after the
                                         * insertion position. */
    Tcl_Interp *interp,                 /* Interpreter to report results. */
    const char *switchName,             /* Not used. */
    Tcl_Obj *objPtr,                    /* String representation */
    char *record,                       /* Structure record */
    int offset,                         /* Not used. */
    int flags)                          /* Indicates whether this is a row
                                         * or column index. */
{
    InsertRowSwitches *insertPtr = (InsertRowSwitches *)record;
    BLT_TABLE table;
    BLT_TABLE_ROW row;

    table = insertPtr->cmdPtr->table;
    if (strcmp(switchName, "-after") == 0) {
        insertPtr->flags = INSERT_AFTER;
    } else {
        insertPtr->flags = INSERT_BEFORE;
    }
    row = blt_table_get_row(interp, table, objPtr);
    if (row == NULL) {
        return TCL_ERROR;
    }
    insertPtr->destRow = row;
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * AfterColumnSwitch --
 *
 *      Convert a Tcl_Obj representing an offset in the table.
 *
 * Results:
 *      The return value is a standard TCL result.
 *
 *---------------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
AfterColumnSwitch(
    ClientData clientData,              /* Flag indicating if the node is
                                         * considered before or after the
                                         * insertion position. */
    Tcl_Interp *interp,                 /* Interpreter to report results. */
    const char *switchName,             /* Not used. */
    Tcl_Obj *objPtr,                    /* String representation */
    char *record,                       /* Structure record */
    int offset,                         /* Not used. */
    int flags)                          /* Indicates whether this is a row
                                         * or column index. */
{
    InsertColumnSwitches *insertPtr = (InsertColumnSwitches *)record;
    BLT_TABLE table;
    BLT_TABLE_COLUMN col;

    table = insertPtr->cmdPtr->table;
    if (strcmp(switchName, "-after") == 0) {
        insertPtr->flags = INSERT_AFTER;
    } else {
        insertPtr->flags = INSERT_BEFORE;
    }
    col = blt_table_get_column(interp, table, objPtr);
    if (col == NULL) {
        return TCL_ERROR;
    }
    insertPtr->destColumn = col;
    return TCL_OK;
}


/*
 *---------------------------------------------------------------------------
 *
 * blt_table_get_column_iter_free_proc --
 *
 *      Free the storage associated with the -columns switch.
 *
 * Results:
 *      None.
 *
 *---------------------------------------------------------------------------
 */
/*ARGSUSED*/
void
blt_table_column_iter_free_proc(ClientData clientData, char *record, 
                                int offset, int flags)
{
    BLT_TABLE_ITERATOR *iterPtr = (BLT_TABLE_ITERATOR *)(record + offset);

    blt_table_free_iterator_objv(iterPtr);
}

/*
 *---------------------------------------------------------------------------
 *
 * blt_table_column_iter_switch_proc --
 *
 *      Convert a Tcl_Obj representing an offset in the table.
 *
 * Results:
 *      The return value is a standard TCL result.
 *
 *---------------------------------------------------------------------------
 */
/*ARGSUSED*/
int
blt_table_column_iter_switch_proc(
    ClientData clientData,              /* Flag indicating if the node is
                                         * considered before or after the
                                         * insertion position. */
    Tcl_Interp *interp,                 /* Interpreter to report results. */
    const char *switchName,             /* Not used. */
    Tcl_Obj *objPtr,                    /* String representation */
    char *record,                       /* Structure record */
    int offset,                         /* Offset to field in structure */
    int flags)                          /* Not used. */
{
    BLT_TABLE_ITERATOR *iterPtr = (BLT_TABLE_ITERATOR *)(record + offset);
    BLT_TABLE table;
    Tcl_Obj **objv;
    int objc;

    table = clientData;
    if (Tcl_ListObjGetElements(interp, objPtr, &objc, &objv) != TCL_OK) {
        return TCL_ERROR;
    }
    if (blt_table_iterate_columns_objv(interp, table, objc, objv, iterPtr)
        != TCL_OK) {
        return TCL_ERROR;
    }
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * blt_table_row_iter_free_proc --
 *
 *      Free the storage associated with the -rows switch.
 *
 * Results:
 *      None.
 *
 *---------------------------------------------------------------------------
 */
/*ARGSUSED*/
void
blt_table_row_iter_free_proc(ClientData clientData, char *record, int offset, 
                             int flags)
{
    BLT_TABLE_ITERATOR *iterPtr = (BLT_TABLE_ITERATOR *)(record + offset);

    blt_table_free_iterator_objv(iterPtr);
}

/*
 *---------------------------------------------------------------------------
 *
 * blt_table_row_iter_switch_proc --
 *
 *      Convert a Tcl_Obj representing an offset in the table.
 *
 * Results:
 *      The return value is a standard TCL result.
 *
 *---------------------------------------------------------------------------
 */
/*ARGSUSED*/
int
blt_table_row_iter_switch_proc(
    ClientData clientData,              /* Flag indicating if the node is
                                         * considered before or after the
                                         * insertion position. */
    Tcl_Interp *interp,                 /* Interpreter to report results. */
    const char *switchName,             /* Not used. */
    Tcl_Obj *objPtr,                    /* String representation */
    char *record,                       /* Structure record */
    int offset,                         /* Offset to field in structure */
    int flags)                          /* Not used. */
{
    BLT_TABLE_ITERATOR *iterPtr = (BLT_TABLE_ITERATOR *)(record + offset);
    BLT_TABLE table;
    Tcl_Obj **objv;
    int objc;

    table = clientData;
    if (Tcl_ListObjGetElements(interp, objPtr, &objc, &objv) != TCL_OK) {
        return TCL_ERROR;
    }
    if (blt_table_iterate_rows_objv(interp, table, objc, objv, iterPtr)
        != TCL_OK) {
        return TCL_ERROR;
    }
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * TableSwitchProc --
 *
 *      Convert a Tcl_Obj representing an offset in the table.
 *
 * Results:
 *      The return value is a standard TCL result.
 *
 *---------------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
TableSwitchProc(
    ClientData clientData,              /* Not used. */
    Tcl_Interp *interp,                 /* Interpreter to report result. */
    const char *switchName,             /* Not used. */
    Tcl_Obj *objPtr,                    /* String representation */
    char *record,                       /* Structure record */
    int offset,                         /* Offset to field in structure */
    int flags)                          /* Not used. */
{
    BLT_TABLE *tablePtr = (BLT_TABLE *)(record + offset);
    BLT_TABLE table;

    if (blt_table_open(interp, Tcl_GetString(objPtr), &table) != TCL_OK) {
        return TCL_ERROR;
    }
    *tablePtr = table;
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * TableFreeProc --
 *
 *      Free the storage associated with the -table switch.
 *
 * Results:
 *      None.
 *
 *---------------------------------------------------------------------------
 */
/*ARGSUSED*/
static void
TableFreeProc(ClientData clientData, char *record, int offset, int flags)
{
    BLT_TABLE table = *(BLT_TABLE *)(record + offset);

    blt_table_close(table);
}

/*
 *---------------------------------------------------------------------------
 *
 * ColumnTypeSwitchProc --
 *
 *      Convert a Tcl_Obj representing the type of the values in a table
 *      column.
 *
 * Results:
 *      The return value is a standard TCL result.
 *
 *---------------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
ColumnTypeSwitchProc(
    ClientData clientData,              /* Not used. */
    Tcl_Interp *interp,                 /* Interpreter to report results. */
    const char *switchName,             /* Not used. */
    Tcl_Obj *objPtr,                    /* String representation */
    char *record,                       /* Structure record */
    int offset,                         /* Offset to field in structure */
    int flags)                          /* Not used. */
{
    BLT_TABLE_COLUMN_TYPE *typePtr = (BLT_TABLE_COLUMN_TYPE *)(record + offset);
    BLT_TABLE_COLUMN_TYPE type;

    type = blt_table_name_to_column_type(Tcl_GetString(objPtr));
    if (type == TABLE_COLUMN_TYPE_UNKNOWN) {
        Tcl_AppendResult(interp, "unknown table column type \"",
                Tcl_GetString(objPtr), "\"", (char *)NULL);
        return TCL_OK;
    }
    *typePtr = type;
    return TCL_OK;
}

static int
MakeRows(Tcl_Interp *interp, BLT_TABLE table, Tcl_Obj *objPtr)
{
    const char *string;
    BLT_TABLE_ROWCOLUMN_SPEC spec;
    long index;

    spec = blt_table_row_spec(table, objPtr, &string);
    switch(spec) {
    case TABLE_SPEC_UNKNOWN:
    case TABLE_SPEC_LABEL:
        Tcl_ResetResult(interp);
        if (blt_table_create_row(interp, table, string) == NULL) {
            return TCL_ERROR;
        }
        break;

    case TABLE_SPEC_INDEX:
        Tcl_ResetResult(interp);
        if (Blt_GetLong(interp, string, &index) != TCL_OK) {
            return TCL_ERROR;
        }
        if (index < 0) {
            Tcl_AppendResult(interp, "invalid row index \"", string, "\"",
                             (char *)NULL);
            return TCL_ERROR;
        }
        /* Index is beyond the end of the table. Auto-create rows.  */
        if (index >= blt_table_num_rows(table)) {
            size_t extra;
            
            extra = (index + 1) - blt_table_num_rows(table);
            blt_table_extend_rows(interp, table, extra, NULL);
        }
        break;
    default:
        return TCL_ERROR;
    }
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * IterateRowsWithCreate --
 *
 *      This is different from the blt_table_iterate_rows routine.  If the
 *      row can't be found but looks like an index or label, we
 *      automatically create the row.
 *      
 *---------------------------------------------------------------------------
 */
static int
IterateRowsWithCreate(Tcl_Interp *interp, BLT_TABLE table, Tcl_Obj *objPtr, 
                      BLT_TABLE_ITERATOR *iterPtr)
{
    if (blt_table_iterate_rows(NULL, table, objPtr, iterPtr) != TCL_OK) {
        /* 
         * We could not parse the row descriptor. If the row specification
         * is a label or index that doesn't exist, create the new rows and
         * try to load the iterator again.
         */
#ifdef notdef
        fprintf(stderr, "making row %s\n", Tcl_GetString(objPtr));
#endif
        if (MakeRows(interp, table, objPtr) != TCL_OK) {
            return TCL_ERROR;
        }
    }
    if (blt_table_iterate_rows(interp, table, objPtr, iterPtr) != TCL_OK) {
        return TCL_ERROR;
    }
    return TCL_OK;
}

static int
MakeColumns(Tcl_Interp *interp, BLT_TABLE table, Tcl_Obj *objPtr)
{
    const char *string;
    BLT_TABLE_ROWCOLUMN_SPEC spec;
    long index;

    spec = blt_table_column_spec(table, objPtr, &string);
    switch(spec) {
    case TABLE_SPEC_UNKNOWN:
    case TABLE_SPEC_LABEL:
        Tcl_ResetResult(interp);
        if (blt_table_create_column(interp, table, string) == NULL) {
            return TCL_ERROR;
        }
        break;
    case TABLE_SPEC_INDEX:
        Tcl_ResetResult(interp);
        if (Blt_GetLong(interp, string, &index) != TCL_OK) {
            return TCL_ERROR;
        }
        if (index < 0) {
            Tcl_AppendResult(interp, "invalid column index \"", string, "\"",
                             (char *)NULL);
            return TCL_ERROR;
        }
        /* Index is beyond the end of the table. Auto-create columns.  */
        if (index >= blt_table_num_columns(table)) {
            size_t extra;

            extra = (index + 1) - blt_table_num_columns(table);
            blt_table_extend_columns(interp, table, extra, NULL);
        }
        break;
    default:
        return TCL_ERROR;
    }
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * IterateColumnsWithCreate --
 *
 *      This is different from the blt_table_iterate_columns routine.  If
 *      the column can't be found but looks like an index or label, we
 *      automatically create the column.
 *      
 *---------------------------------------------------------------------------
 */
static int
IterateColumnsWithCreate(Tcl_Interp *interp, BLT_TABLE table, Tcl_Obj *objPtr, 
                         BLT_TABLE_ITERATOR *iterPtr)
{
    if (blt_table_iterate_columns(interp, table, objPtr, iterPtr) != TCL_OK) {
        /* 
         * We could not parse column descriptor.  If the column
         * specification is a label that doesn't exist, create a new column
         * with that label and try to load the iterator again.
         */
#ifdef notdef
        fprintf(stderr, "making column %s\n", Tcl_GetString(objPtr));
#endif
        if (MakeColumns(interp, table, objPtr) != TCL_OK) {
            return TCL_ERROR;
        }
    }
    if (blt_table_iterate_columns(interp, table, objPtr, iterPtr) != TCL_OK) {
        return TCL_ERROR;
    }
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * GetTableCmdInterpData --
 *
 *---------------------------------------------------------------------------
 */

static TableCmdInterpData *
GetTableCmdInterpData(Tcl_Interp *interp)
{
    TableCmdInterpData *dataPtr;
    Tcl_InterpDeleteProc *proc;

    dataPtr = (TableCmdInterpData *)
        Tcl_GetAssocData(interp, TABLE_THREAD_KEY, &proc);
    if (dataPtr == NULL) {
        dataPtr = Blt_AssertMalloc(sizeof(TableCmdInterpData));
        dataPtr->interp = interp;
        Tcl_SetAssocData(interp, TABLE_THREAD_KEY, TableInterpDeleteProc, 
                dataPtr);
        Blt_InitHashTable(&dataPtr->instTable, BLT_STRING_KEYS);
        Blt_InitHashTable(&dataPtr->fmtTable,  BLT_STRING_KEYS);
        Blt_InitHashTable(&dataPtr->findTable, BLT_ONE_WORD_KEYS);
    }
    return dataPtr;
}

/*
 *---------------------------------------------------------------------------
 *
 * NewTableCmd --
 *
 *      This is a helper routine used by TableCreateOp.  It create a new
 *      instance of a table command.  Memory is allocated for the command
 *      structure and a new TCL command is created (same as the instance
 *      name).  All table commands have hash table entries in a global
 *      (interpreter-specific) registry.
 *      
 * Results:
 *      Returns a pointer to the newly allocated table command structure.
 *
 * Side Effects:
 *      Memory is allocated for the structure and a hash table entry is
 *      added.  
 *
 *---------------------------------------------------------------------------
 */
static Cmd *
NewTableCmd(Tcl_Interp *interp, BLT_TABLE table, const char *name)
{
    Cmd *cmdPtr;
    TableCmdInterpData *dataPtr;
    int isNew;

    cmdPtr = Blt_AssertCalloc(1, sizeof(Cmd));
    cmdPtr->table = table;
    cmdPtr->interp = interp;
    cmdPtr->emptyString = Blt_AssertStrdup("");

    Blt_InitHashTable(&cmdPtr->traceTable, BLT_STRING_KEYS);
    Blt_InitHashTable(&cmdPtr->watchTable, BLT_STRING_KEYS);

    cmdPtr->cmdToken = Tcl_CreateObjCommand(interp, name, TableInstObjCmd, 
        cmdPtr, TableInstDeleteProc);
    dataPtr = GetTableCmdInterpData(interp);
    cmdPtr->tablePtr = &dataPtr->instTable;
    cmdPtr->hPtr = Blt_CreateHashEntry(&dataPtr->instTable, name, &isNew);
    Blt_SetHashValue(cmdPtr->hPtr, cmdPtr);
    return cmdPtr;
}

/*
 *---------------------------------------------------------------------------
 *
 * GenerateName --
 *
 *      Generates an unique table command name.  Table names are in the
 *      form "datatableN", where N is a non-negative integer. Check each
 *      name generated to see if it is already a table. We want to recycle
 *      names if possible.
 *      
 * Results:
 *      Returns the unique name.  The string itself is stored in the dynamic
 *      string passed into the routine.
 *
 *---------------------------------------------------------------------------
 */
static const char *
GenerateName(Tcl_Interp *interp, const char *prefix, const char *suffix,
             Tcl_DString *resultPtr)
{

    int n;
    const char *instName;

    /* 
     * Parse the command and put back so that it's in a consistent format.
     *
     *  t1         <current namespace>::t1
     *  n1::t1     <current namespace>::n1::t1
     *  ::t1       ::t1
     *  ::n1::t1   ::n1::t1
     */
    instName = NULL;                    /* Suppress compiler warning. */
    for (n = 0; n < INT_MAX; n++) {
        Blt_ObjectName objName;
        Tcl_DString ds;
        char string[200];

        Tcl_DStringInit(&ds);
        Tcl_DStringAppend(&ds, prefix, -1);
        Blt_FmtString(string, 200, "datatable%d", n);
        Tcl_DStringAppend(&ds, string, -1);
        Tcl_DStringAppend(&ds, suffix, -1);
        if (!Blt_ParseObjectName(interp, Tcl_DStringValue(&ds), 
                                 &objName, 0)) {
            return NULL;
        }
        instName = Blt_MakeQualifiedName(&objName, resultPtr);
        Tcl_DStringFree(&ds);
        /* 
         * Check if the command already exists. 
         */
        if (Blt_CommandExists(interp, instName)) {
            continue;
        }
        if (!blt_table_exists(interp, instName)) {
            /* 
             * We want the name of the table command and the underlying
             * table object to be the same. Check that the free command
             * name isn't an already a table object name.
             */
            break;
        }
    }
    return instName;
}

/*
 *---------------------------------------------------------------------------
 *
 * GetTableCmd --
 *
 *      Find the table command associated with the TCL command "string".
 *      
 *      We have to perform multiple lookups to get this right.  
 *
 *      The first step is to generate a canonical command name.  If an
 *      unqualified command name (i.e. no namespace qualifier) is given, we
 *      should search first the current namespace and then the global one.
 *      Most TCL commands (like Tcl_GetCmdInfo) look only at the global
 *      namespace.
 *
 *      Next check if the string is 
 *              a) a TCL command and 
 *              b) really is a command for a table object.  
 *      Tcl_GetCommandInfo will get us the objClientData field that should be
 *      a cmdPtr.  We verify that by searching our hashtable of cmdPtr
 *      addresses.
 *
 * Results:
 *      A pointer to the table command.  If no associated table command can
 *      be found, NULL is returned.  It's up to the calling routines to
 *      generate an error message.
 *
 *---------------------------------------------------------------------------
 */
static Cmd *
GetTableCmd(Tcl_Interp *interp, const char *name)
{
    Blt_HashEntry *hPtr;
    Tcl_DString ds;
    TableCmdInterpData *dataPtr;
    Blt_ObjectName objName;
    const char *qualName;

    /* Put apart the table name and put is back together in a standard
     * format. */
    if (!Blt_ParseObjectName(interp, name, &objName, BLT_NO_ERROR_MSG)) {
        return NULL;            /* No such parent namespace. */
    }
    /* Rebuild the fully qualified name. */
    qualName = Blt_MakeQualifiedName(&objName, &ds);
    dataPtr = GetTableCmdInterpData(interp);
    hPtr = Blt_FindHashEntry(&dataPtr->instTable, qualName);
    Tcl_DStringFree(&ds);
    if (hPtr == NULL) {
        return NULL;
    }
    return Blt_GetHashValue(hPtr);
}

/*
 *---------------------------------------------------------------------------
 *
 * GetTraceFlags --
 *
 *      Parses a string representation of the trace bit flags and returns
 *      the mask.
 *
 * Results:
 *      The trace mask is returned.
 *
 *---------------------------------------------------------------------------
 */
static int
GetTraceFlags(const char *string)
{
    const char *p;
    unsigned int flags;

    flags = 0;
    for (p = string; *p != '\0'; p++) {
        switch (toupper(UCHAR(*p))) {
        case 'R':
            flags |= TABLE_TRACE_READS; break;
        case 'W':
            flags |= TABLE_TRACE_WRITES;        break;
        case 'U':
            flags |= TABLE_TRACE_UNSETS;        break;
        case 'C':
            flags |= TABLE_TRACE_CREATES;       break;
        default:
            return -1;
        }
    }
    return flags;
}

/*
 *---------------------------------------------------------------------------
 *
 * PrintTraceFlags --
 *
 *      Generates a string representation of the trace bit flags.  It's
 *      assumed that the provided string is at least 5 bytes.
 *
 * Results:
 *      None.
 *
 * Side Effects:
 *      The bitflag information is written to the provided string.
 *
 *---------------------------------------------------------------------------
 */
static void
PrintTraceFlags(unsigned int flags, char *string)
{
    char *p;

    p = string;
    if (flags & TABLE_TRACE_READS) {
        *p++ = 'r';
    } 
    if (flags & TABLE_TRACE_WRITES) {
        *p++ = 'w';
    } 
    if (flags & TABLE_TRACE_UNSETS) {
        *p++ = 'u';
    } 
    if (flags & TABLE_TRACE_CREATES) {
        *p++ = 'c';
    } 
    *p = '\0';
}

static void
PrintTraceInfo(Tcl_Interp *interp, TraceInfo *tiPtr, Tcl_Obj *listObjPtr)
{
    char string[5];
    struct _BLT_TABLE_TRACE *tracePtr;

    Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewStringObj("name", 4));
    Tcl_ListObjAppendElement(interp, listObjPtr, 
        Tcl_NewStringObj(tiPtr->hPtr->key.string, -1));
    tracePtr = tiPtr->trace;
    if (tracePtr->rowTag != NULL) {
        Tcl_ListObjAppendElement(interp, listObjPtr, 
                Tcl_NewStringObj("row", 3));
        Tcl_ListObjAppendElement(interp, listObjPtr, 
                Tcl_NewStringObj(tracePtr->rowTag, -1));
    }
    if (tracePtr->row != NULL) {
        Tcl_ListObjAppendElement(interp, listObjPtr, 
                                 Tcl_NewStringObj("row", 3));
        Tcl_ListObjAppendElement(interp, listObjPtr, 
             GetRowIndexObj(tracePtr->table, tracePtr->row));
    }
    if (tracePtr->colTag != NULL) {
        Tcl_ListObjAppendElement(interp, listObjPtr, 
                Tcl_NewStringObj("column", 6));
        Tcl_ListObjAppendElement(interp, listObjPtr, 
                Tcl_NewStringObj(tracePtr->colTag, -1));
    }
    if (tracePtr->column != NULL) {
        Tcl_ListObjAppendElement(interp, listObjPtr, 
                                 Tcl_NewStringObj("column", 6));
        Tcl_ListObjAppendElement(interp, listObjPtr, 
          GetColumnIndexObj(tracePtr->table, tracePtr->column));
    }
    PrintTraceFlags(tracePtr->flags, string);
    Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewStringObj("flags", 5));
    Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewStringObj(string, -1));

    Tcl_ListObjAppendElement(interp, listObjPtr, 
        Tcl_NewStringObj("command", 7));
    Tcl_ListObjAppendElement(interp, listObjPtr, tiPtr->cmdObjPtr);
}

/*
 *---------------------------------------------------------------------------
 *
 * FreeWatchInfo --
 *
 *      This is a helper routine used to delete notifiers.  It releases the
 *      Tcl_Objs used in the notification callback command and the actual
 *      table notifier.  Memory for the notifier is also freed.
 *
 * Results:
 *      None.
 *
 * Side Effects:
 *      Memory is deallocated and the notitifer is no longer active.
 *
 *---------------------------------------------------------------------------
 */
static void
FreeWatchInfo(WatchInfo *watchPtr)
{
    Tcl_DecrRefCount(watchPtr->cmdObjPtr);
    blt_table_delete_notifier(watchPtr->cmdPtr->table, watchPtr->notifier);
    Blt_Free(watchPtr);
}

/*
 *---------------------------------------------------------------------------
 *
 * TraceProc --
 *
 *---------------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
TraceProc(ClientData clientData, BLT_TABLE_TRACE_EVENT *eventPtr)
{
    TraceInfo *tracePtr = clientData; 
    char string[5];
    int result;
    Tcl_Obj *cmdObjPtr, *objPtr;
    Tcl_Interp *interp;

    interp = eventPtr->interp;
    cmdObjPtr = Tcl_DuplicateObj(tracePtr->cmdObjPtr);
    objPtr = GetRowIndexObj(eventPtr->table, eventPtr->row);
    Tcl_ListObjAppendElement(interp, cmdObjPtr, objPtr);
    objPtr = GetColumnIndexObj(eventPtr->table, eventPtr->column);
    Tcl_ListObjAppendElement(interp, cmdObjPtr, objPtr);
    PrintTraceFlags(eventPtr->mask, string);
    objPtr = Tcl_NewStringObj(string, -1);
    Tcl_ListObjAppendElement(interp, cmdObjPtr, objPtr);

    Tcl_IncrRefCount(cmdObjPtr);
    result = Tcl_EvalObjEx(interp, cmdObjPtr, TCL_EVAL_GLOBAL);
    Tcl_DecrRefCount(cmdObjPtr);
    if (result != TCL_OK) {
        Tcl_BackgroundError(eventPtr->interp);
    }
    return result;
}

/*
 *---------------------------------------------------------------------------
 *
 * TraceDeleteProc --
 *
 *---------------------------------------------------------------------------
 */
/*ARGSUSED*/
static void
TraceDeleteProc(ClientData clientData)
{
    TraceInfo *tracePtr = clientData; 

    Tcl_DecrRefCount(tracePtr->cmdObjPtr);
    if (tracePtr->hPtr != NULL) {
        Blt_DeleteHashEntry(tracePtr->tablePtr, tracePtr->hPtr);
    }
    Blt_Free(tracePtr);
}

static const char *
GetEventName(int type)
{
    if (type & TABLE_NOTIFY_CREATE) {
        return "-create";
    } 
    if (type & TABLE_NOTIFY_DELETE) {
        return "-delete";
    }
    if (type & TABLE_NOTIFY_MOVE) {
        return "-move";
    }
    if (type & TABLE_NOTIFY_RELABEL) {
        return "-relabel";
    }
    return "???";
}

/*
 *---------------------------------------------------------------------------
 *
 * NotifierDeleteProc --
 *
 *---------------------------------------------------------------------------
 */
/*ARGSUSED*/
static void
NotifierDeleteProc(ClientData clientData)
{
    WatchInfo *watchPtr = clientData;

    FreeWatchInfo(watchPtr);
}

/*
 *---------------------------------------------------------------------------
 *
 * NotifyProc --
 *
 *---------------------------------------------------------------------------
 */
static int
NotifyProc(ClientData clientData, BLT_TABLE_NOTIFY_EVENT *eventPtr)
{
    WatchInfo *watchPtr = clientData; 
    Tcl_Interp *interp;
    int result;
    size_t index;
    Tcl_Obj *objPtr, *cmdObjPtr;

    interp = watchPtr->cmdPtr->interp;

    cmdObjPtr = Tcl_DuplicateObj(watchPtr->cmdObjPtr);
    objPtr = Tcl_NewStringObj(GetEventName(eventPtr->type), -1);
    Tcl_ListObjAppendElement(interp, cmdObjPtr, objPtr);
    if (eventPtr->type & TABLE_NOTIFY_ROW) {
        index = blt_table_row_index(eventPtr->table, eventPtr->row);
    } else {
        index = blt_table_column_index(eventPtr->table, eventPtr->column);
    }   
    objPtr = Tcl_NewWideIntObj(index);
    Tcl_ListObjAppendElement(interp, cmdObjPtr, objPtr);
    Tcl_IncrRefCount(cmdObjPtr);
    result = Tcl_EvalObjEx(interp, cmdObjPtr, TCL_EVAL_GLOBAL);
    Tcl_DecrRefCount(cmdObjPtr);
    if (result != TCL_OK) {
        Tcl_BackgroundError(interp);
        return TCL_ERROR;
    }
    Tcl_ResetResult(interp);
    return TCL_OK;
}

static int
ColumnVarResolverProc(
    Tcl_Interp *interp,                 /* Current interpreter. */
    const char *varName,                   /* Variable name being resolved. */
    Tcl_Namespace *nsPtr,               /* Current namespace context. */
    int flags,                          /* TCL_LEAVE_ERR_MSG => leave error
                                         * message. */
    Tcl_Var *varPtr)                    /* (out) Resolved variable. */ 
{
    Blt_HashEntry *hPtr;
    BLT_TABLE_COLUMN col;
    FindSwitches *switchesPtr;
    TableCmdInterpData *dataPtr;
    Tcl_Obj *valueObjPtr;
    long index;
    char c;
    const char *prefix;
    int prefixLen;

    dataPtr = GetTableCmdInterpData(interp);
    hPtr = Blt_FindHashEntry(&dataPtr->findTable, nsPtr);
    if (hPtr == NULL) {
        /* This should never happen.  We can't find data associated with
         * the current namespace, but this routine should never be called
         * unless we're in a namespace that with linked with this variable
         * resolver. */
        return TCL_CONTINUE;    
    }
    switchesPtr = Blt_GetHashValue(hPtr);
    prefix = NULL;
    prefixLen = 0;
    if (switchesPtr->prefixObjPtr != NULL) {
        prefix = Tcl_GetStringFromObj(switchesPtr->prefixObjPtr, &prefixLen);
    }
    c = varName[0];
    col = NULL;
    if ((c == '#') && (strcmp(varName, "#") == 0)) {
        /* $# returns the row number of the current row. */
        /* Look up the column from the variable name given. */
        valueObjPtr = GetRowIndexObj(switchesPtr->table, switchesPtr->row);
        *varPtr = Blt_GetCachedVar(&switchesPtr->varTable,varName, valueObjPtr);
        return TCL_OK;
    } 
    if ((isdigit(c)) &&
        (Blt_GetLong(NULL, (const char *)varName, &index) == TCL_OK)) {
        /* $N returns the value in the Nth column of the current row. */
        col = blt_table_get_column_by_index(switchesPtr->table, index);
    } else if (prefixLen == 0) {
        /* $fieldName returns the value of the "fieldName" column of the
         * current row. */
        col = blt_table_get_column_by_label(switchesPtr->table, varName);
    } else if (strncmp(varName, prefix, prefixLen) == 0) {
        /* $pref_fieldName returns the value of the "fieldName" column of
         * the current row. "pref_" is the user-defined prefix. */
        col = blt_table_get_column_by_label(switchesPtr->table, 
                                            varName + prefixLen);
    }
    if (col == NULL) {
        /* Variable name doesn't refer to any column. Pass it back to the
         * TCL interpreter and let it resolve the variable as usual. */
        return TCL_CONTINUE;
    }
    valueObjPtr = blt_table_get_obj(switchesPtr->table, switchesPtr->row, col);
    if (valueObjPtr == NULL) {
        valueObjPtr = switchesPtr->emptyValueObjPtr;
        if (valueObjPtr != NULL) {
            Tcl_IncrRefCount(valueObjPtr);
        } else {
            return TCL_CONTINUE;
        }
    }
    *varPtr = Blt_GetCachedVar(&switchesPtr->varTable, varName, valueObjPtr);
    return TCL_OK;
}

static int
EvaluateExpr(Tcl_Interp *interp, BLT_TABLE table, Tcl_Obj *exprObjPtr, 
             int *boolPtr)
{
    Tcl_Obj *resultObjPtr;
    int bool;

    if (Tcl_ExprObj(interp, exprObjPtr, &resultObjPtr) != TCL_OK) {
        return TCL_ERROR;
    }
    if (Tcl_GetBooleanFromObj(interp, resultObjPtr, &bool) != TCL_OK) {
        return TCL_ERROR;
    }
    Tcl_DecrRefCount(resultObjPtr);
    *boolPtr = bool;
    return TCL_OK;
}

static int
FindRows(Tcl_Interp *interp, BLT_TABLE table, Tcl_Obj *objPtr, 
         FindSwitches *switchesPtr)
{
    Blt_HashEntry *hPtr;
    BLT_TABLE_ROW row;
    TableCmdInterpData *dataPtr;
    Tcl_Namespace *nsPtr;
    Tcl_Obj *listObjPtr;
    int isNew;
    size_t numMatches;
    int result = TCL_OK;

    Tcl_AddInterpResolvers(interp, TABLE_FIND_KEY, (Tcl_ResolveCmdProc*)NULL,
        ColumnVarResolverProc, (Tcl_ResolveCompiledVarProc*)NULL);

    dataPtr = GetTableCmdInterpData(interp);
    nsPtr = Tcl_GetCurrentNamespace(interp);
    hPtr = Blt_CreateHashEntry(&dataPtr->findTable, nsPtr, &isNew);
    assert(isNew);
    Blt_SetHashValue(hPtr, switchesPtr);

    /* Now process each row, evaluating the expression. */
    listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL);
    numMatches = 0;
    for (row = blt_table_first_tagged_row(&switchesPtr->iter); row != NULL; 
         row = blt_table_next_tagged_row(&switchesPtr->iter)) {
        int bool;
        
        switchesPtr->row = row;
        result = EvaluateExpr(interp, table, objPtr, &bool);
        if (result != TCL_OK) {
            break;
        }
        if (switchesPtr->flags & FIND_INVERT) {
            bool = !bool;
        }
        if (bool) {
            Tcl_Obj *objPtr;

            if (switchesPtr->tag != NULL) {
                result = blt_table_set_row_tag(interp, table, row,
                        switchesPtr->tag);
                if (result != TCL_OK) {
                    break;
                }
            }
            numMatches++;
            objPtr = GetRowIndexObj(table, row);
            Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
            if ((switchesPtr->maxMatches > 0) && 
                (numMatches >= switchesPtr->maxMatches)) {
                break;
            }
        }
    }
    if (result != TCL_OK) {
        Tcl_DecrRefCount(listObjPtr);
    } else {
        Tcl_SetObjResult(interp, listObjPtr);
    }
    /* Clean up. */
    Blt_DeleteHashEntry(&dataPtr->findTable, hPtr);
    Blt_FreeCachedVars(&switchesPtr->varTable);
    if (!Tcl_RemoveInterpResolvers(interp, TABLE_FIND_KEY)) {
        Tcl_AppendResult(interp, "can't delete resolver scheme", 
                (char *)NULL);
        return TCL_ERROR;
    }
    return result;
}

static int
AppendColumn(Tcl_Interp *interp, BLT_TABLE src, BLT_TABLE dst,
    BLT_TABLE_COLUMN srcCol,            /* Column in the source table. */
    BLT_TABLE_COLUMN dstCol)            /* Column in the dstination table. */
{
    size_t i, j, oldNumRows, need;
    BLT_TABLE_COLUMN_TYPE type;
    
    oldNumRows = blt_table_num_rows(src);
    need = oldNumRows + blt_table_num_rows(dst);
    if (blt_table_extend_rows(interp, dst, need, NULL) != TCL_OK) {
        return TCL_ERROR;
    }
    for (i = 0, j = oldNumRows; i < blt_table_num_rows(src); i++, j++) {
        BLT_TABLE_ROW srcRow, dstRow;
        BLT_TABLE_VALUE value;

        srcRow = blt_table_row(src, i);
        value = blt_table_get_value(src, srcRow, srcCol);
        if (value == NULL) {
            continue;
        }
        dstRow = blt_table_row(dst, j);
        if (blt_table_set_value(dst, dstRow, dstCol, value) != TCL_OK) {
            return TCL_ERROR;
        }
    }
    type = blt_table_column_type(dstCol);
    if (blt_table_set_column_type(interp, dst, dstCol, type) != TCL_OK) {
        return TCL_ERROR;
    }
    return TCL_OK;
}           

static int
CopyColumn(Tcl_Interp *interp, BLT_TABLE src, BLT_TABLE dst,
    BLT_TABLE_COLUMN scol,                /* Column in the source table. */
    BLT_TABLE_COLUMN dcol)                /* Column in the destination table. */
{
    BLT_TABLE_ROW srow, drow;
    size_t i, srcNumRows, dstNumRows;

    if ((blt_table_same_object(src, dst)) && (scol == dcol)) {
        return TCL_OK;                  /* Source and destination columns are
                                         * the same column in the same
                                         * table. */
    }
    srcNumRows = blt_table_num_rows(src);
    dstNumRows = blt_table_num_rows(dst);
    if (srcNumRows >  dstNumRows) {
        size_t need;

        need = (srcNumRows - dstNumRows);
        if (blt_table_extend_rows(interp, dst, need, NULL) != TCL_OK) {
            return TCL_ERROR;
        }
    }
    if (blt_table_set_column_type(interp, dst, dcol,
        blt_table_column_type(scol)) != TCL_OK) {
        return TCL_ERROR;
    }
    for (srow = blt_table_first_row(src), drow = blt_table_first_row(dst);
         srow != NULL;
         srow = blt_table_next_row(srow), drow = blt_table_next_row(drow)) {
        BLT_TABLE_VALUE value;
        
        value = blt_table_get_value(src, srow, scol);
        if (value == NULL) {
            continue;
        }
        if (blt_table_set_value(dst, drow, dcol, value) != TCL_OK) {
            return TCL_ERROR;
        }
    }
    for (i = srcNumRows; i < dstNumRows; i++) {
        BLT_TABLE_ROW drow;

        drow = blt_table_row(dst, i);
        blt_table_unset_value(dst, drow, dcol);
    }
    return TCL_OK;
}           

static void
CopyColumnTags(BLT_TABLE src, BLT_TABLE dst,
    BLT_TABLE_COLUMN c1,        /* Column in the source table. */
    BLT_TABLE_COLUMN c2)        /* Column in the dstination table. */
{
    Blt_Chain chain;
    Blt_ChainLink link;

    /* Find all tags for with this column index. */
    chain = blt_table_get_column_tags(src, c1);
    for (link = Blt_Chain_FirstLink(chain); link != NULL;
         link = Blt_Chain_NextLink(link)) {
        const char *tag;

        tag = Blt_Chain_GetValue(link);
        blt_table_set_column_tag(NULL, dst, c2, tag);
    }
}           

static void
ClearTable(BLT_TABLE table) 
{
    BLT_TABLE_COLUMN col, nextCol;
    BLT_TABLE_ROW row, nextRow;

    for (col = blt_table_first_column(table); col != NULL; col = nextCol) {
        nextCol = blt_table_next_column(col);
        blt_table_delete_column(table, col);
    }
    for (row = blt_table_first_row(table); row != NULL;  row = nextRow) {
        nextRow = blt_table_next_row(row);
        blt_table_delete_row(table, row);
    }
}

static int
CopyColumnLabel(Tcl_Interp *interp, BLT_TABLE src, BLT_TABLE dst,
                BLT_TABLE_COLUMN c1, BLT_TABLE_COLUMN c2) 
{
    const char *label;

    label = blt_table_column_label(c1);
    if (blt_table_set_column_label(interp, dst, c2, label) != TCL_OK) {
        return TCL_ERROR;
    }
    return TCL_OK;
}

static int
CopyTable(Tcl_Interp *interp, BLT_TABLE src, BLT_TABLE dst) 
{
    size_t i;

    if (blt_table_same_object(src, dst)) {
        return TCL_OK;                  /* Source and destination are the
                                         * same table. */
    }
    ClearTable(dst);
    if (blt_table_num_columns(src) > blt_table_num_columns(dst)) {
        size_t count;

        count = blt_table_num_columns(src) - blt_table_num_columns(dst);
        blt_table_extend_columns(interp, dst, count, NULL);
    }
    for (i = 0; i < blt_table_num_columns(src); i++) {
        BLT_TABLE_COLUMN c1, c2;

        c1 = blt_table_column(src, i);
        c2 = blt_table_column(dst, i);
        if (CopyColumn(interp, src, dst, c1, c2) != TCL_OK) {
            return TCL_ERROR;
        }
        if (CopyColumnLabel(interp, src, dst, c1, c2) != TCL_OK) {
            return TCL_ERROR;
        }
        CopyColumnTags(src, dst, c1, c2);
    }
    return TCL_OK;
}


static int
CopyRow(Tcl_Interp *interp, BLT_TABLE srcTable, BLT_TABLE destTable,
    BLT_TABLE_ROW srcRow,               /* Row offset in the source table. */
    BLT_TABLE_ROW destRow)              /* Row offset in the destination. */
{
    size_t i;

    if ((blt_table_same_object(srcTable, destTable)) && 
        (srcRow == destRow)) {
        return TCL_OK;          /* Source and destination are the same. */
    }
    if (blt_table_num_columns(srcTable) > blt_table_num_columns(destTable)) {
        size_t needed;

        needed = blt_table_num_columns(srcTable) - 
            blt_table_num_columns(destTable);
        if (blt_table_extend_columns(interp, destTable, needed, NULL)!=TCL_OK) {
            return TCL_ERROR;
        }
    }
    for (i = 0; i < blt_table_num_columns(srcTable); i++) {
        BLT_TABLE_COLUMN col;
        BLT_TABLE_VALUE value;

        col = blt_table_column(srcTable, i);
        value = blt_table_get_value(srcTable, srcRow, col);
        col = blt_table_column(destTable, i);
        if (blt_table_set_value(destTable, destRow, col, value)!= TCL_OK) {
            return TCL_ERROR;
        }
    }
    return TCL_OK;
}           

static void
CopyRowTags(BLT_TABLE srcTable, BLT_TABLE destTable,
    BLT_TABLE_ROW srcRow,               /* Row in the source table. */
    BLT_TABLE_ROW destRow)              /* Row in the destination table. */
{
    Blt_Chain chain;
    Blt_ChainLink link;

    /* Get all tags for this particular row in the source table. */
    chain = blt_table_get_row_tags(srcTable, srcRow);
    for (link = Blt_Chain_FirstLink(chain); link != NULL; 
         link = Blt_Chain_NextLink(link)) {
        const char *tag;

        tag = Blt_Chain_GetValue(link);
        blt_table_set_row_tag(NULL, destTable, destRow, tag);
    }
    Blt_Chain_Destroy(chain);
}           


static int
WriteRecord(Tcl_Interp *interp, Tcl_Channel channel, Tcl_DString *dsPtr)
{
    int length, numWritten;
    char *line;

    length = Tcl_DStringLength(dsPtr);
    line = Tcl_DStringValue(dsPtr);
#if HAVE_UTF
#ifdef notdef
    numWritten = Tcl_WriteChars(channel, line, length);
#endif
    numWritten = Tcl_Write(channel, line, length);
#else
    numWritten = Tcl_Write(channel, line, length);
#endif
    if (numWritten < 0) {
        Tcl_AppendResult(interp, "error writing dump record: ", 
                         Tcl_PosixError(interp), (char *)NULL);
        return FALSE;
    }
    Tcl_DStringSetLength(dsPtr, 0);
    return TRUE;
}

/*
 *---------------------------------------------------------------------------
 *
 * DumpHeader --
 *
 *      Prints the info associated with a column into a dynamic string.
 *
 * Results:
 *      None.
 *
 *---------------------------------------------------------------------------
 */
static int
DumpHeader(Tcl_Interp *interp, DumpSwitches *dumpPtr, size_t numRows, 
           size_t numCols)
{
    /* i rows columns ctime mtime \n */
    Tcl_DStringAppendElement(dumpPtr->dsPtr, "i");

    /* # of rows and columns may be a subset of the table. */
    Tcl_DStringAppendElement(dumpPtr->dsPtr, Blt_Ltoa(numRows));
    Tcl_DStringAppendElement(dumpPtr->dsPtr, Blt_Ltoa(numCols));

    Tcl_DStringAppendElement(dumpPtr->dsPtr, Blt_Ltoa(0));
    Tcl_DStringAppendElement(dumpPtr->dsPtr, Blt_Ltoa(0));
    Tcl_DStringAppend(dumpPtr->dsPtr, "\n", 1);
    if (dumpPtr->channel != NULL) {
        return WriteRecord(interp, dumpPtr->channel, dumpPtr->dsPtr);
    }
    return TRUE;
}


/*
 *---------------------------------------------------------------------------
 *
 * DumpValue --
 *
 *      Retrieves all tags for a given row or column into a tcl list.  
 *
 * Results:
 *      None.
 *
 *---------------------------------------------------------------------------
 */
static int
DumpValue(Tcl_Interp *interp, BLT_TABLE table, DumpSwitches *dumpPtr, 
          BLT_TABLE_ROW row, BLT_TABLE_COLUMN col)
{
    const char *string;

    string = blt_table_get_string(table, row, col);
    if (string == NULL) {
        return TRUE;
    }
    /* d row column value \n */
    Tcl_DStringAppendElement(dumpPtr->dsPtr, "d");
    Tcl_DStringAppendElement(dumpPtr->dsPtr, Blt_Ltoa(blt_table_row_index(table, row)));
    Tcl_DStringAppendElement(dumpPtr->dsPtr, Blt_Ltoa(blt_table_column_index(table, col)));
    Tcl_DStringAppendElement(dumpPtr->dsPtr, string);
    Tcl_DStringAppend(dumpPtr->dsPtr, "\n", 1);
    if (dumpPtr->channel != NULL) {
        return WriteRecord(interp, dumpPtr->channel, dumpPtr->dsPtr);
    }
    return TRUE;
}

/*
 *---------------------------------------------------------------------------
 *
 * DumpColumn --
 *
 *      Prints the info associated with a column into a dynamic string.
 *
 *---------------------------------------------------------------------------
 */
static int
DumpColumn(Tcl_Interp *interp, BLT_TABLE table, DumpSwitches *dumpPtr, 
           BLT_TABLE_COLUMN col)
{
    Blt_Chain colTags;
    Blt_ChainLink link;
    const char *name;

    /* c index label type tags \n */
    Tcl_DStringAppendElement(dumpPtr->dsPtr, "c");
    Tcl_DStringAppendElement(dumpPtr->dsPtr, 
           Blt_Ltoa(blt_table_column_index(table, col)));
    Tcl_DStringAppendElement(dumpPtr->dsPtr, blt_table_column_label(col));
    name = blt_table_column_type_to_name(blt_table_column_type(col));
    if (name == NULL) {
        name = "";
    }
    Tcl_DStringAppendElement(dumpPtr->dsPtr, name);

    colTags = blt_table_get_column_tags(table, col);
    Tcl_DStringStartSublist(dumpPtr->dsPtr);
    for (link = Blt_Chain_FirstLink(colTags); link != NULL;
         link = Blt_Chain_NextLink(link)) {
        const char *tag;

        tag = Blt_Chain_GetValue(link);
        Tcl_DStringAppendElement(dumpPtr->dsPtr, tag);
    }
    Blt_Chain_Destroy(colTags);
    Tcl_DStringEndSublist(dumpPtr->dsPtr);
    Tcl_DStringAppend(dumpPtr->dsPtr, "\n", 1);
    if (dumpPtr->channel != NULL) {
        return WriteRecord(interp, dumpPtr->channel, dumpPtr->dsPtr);
    }
    return TRUE;
}

/*
 *---------------------------------------------------------------------------
 *
 * DumpRow --
 *
 *      Prints the info associated with a row into a dynamic string.
 *
 *---------------------------------------------------------------------------
 */
static int
DumpRow(Tcl_Interp *interp, BLT_TABLE table, DumpSwitches *dumpPtr, 
        BLT_TABLE_ROW row)
{
    Blt_Chain rowTags;
    Blt_ChainLink link;

    /* r index label tags \n */
    Tcl_DStringAppendElement(dumpPtr->dsPtr, "r");
    Tcl_DStringAppendElement(dumpPtr->dsPtr, 
                             Blt_Ltoa(blt_table_row_index(table, row)));
    Tcl_DStringAppendElement(dumpPtr->dsPtr, (char *)blt_table_row_label(row));
    Tcl_DStringStartSublist(dumpPtr->dsPtr);
    rowTags = blt_table_get_row_tags(table, row);
    for (link = Blt_Chain_FirstLink(rowTags); link != NULL;
         link = Blt_Chain_NextLink(link)) {
        const char *tag;

        tag = Blt_Chain_GetValue(link);
        Tcl_DStringAppendElement(dumpPtr->dsPtr, tag);
    }
    Blt_Chain_Destroy(rowTags);
    Tcl_DStringEndSublist(dumpPtr->dsPtr);
    Tcl_DStringAppend(dumpPtr->dsPtr, "\n", 1);
    if (dumpPtr->channel != NULL) {
        return WriteRecord(interp, dumpPtr->channel, dumpPtr->dsPtr);
    }
    return TRUE;
}


/*
 *---------------------------------------------------------------------------
 *
 * DumpTable --
 *
 *      Dumps data from the given table based upon the row and column maps
 *      provided which describe what rows and columns are to be dumped. The
 *      dump information is written to the file named. If the file name
 *      starts with an '@', then it is the name of an already opened
 *      channel to be used.
 *      
 * Results:
 *      A standard TCL result.  If the dump was successful, TCL_OK is
 *      returned.  Otherwise, TCL_ERROR is returned and an error message is
 *      left in the interpreter result.
 *
 * Side Effects:
 *      Dump information is written to the named file.
 *
 *---------------------------------------------------------------------------
 */
static int
DumpTable(Tcl_Interp *interp, BLT_TABLE table, DumpSwitches *dumpPtr)
{
    int result;
    size_t numCols, numRows;
    BLT_TABLE_COLUMN col;
    BLT_TABLE_ROW row;

    if (dumpPtr->ri.chain != NULL) {
        numRows = Blt_Chain_GetLength(dumpPtr->ri.chain);
    } else {
        numRows = blt_table_num_rows(table);
    }
    if (dumpPtr->ci.chain != NULL) {
        numCols = Blt_Chain_GetLength(dumpPtr->ci.chain);
    } else {
        numCols = blt_table_num_columns(table);
    }
    result = DumpHeader(interp, dumpPtr, numRows, numCols);
    for (col = blt_table_first_tagged_column(&dumpPtr->ci); 
         (result) && (col != NULL); 
         col = blt_table_next_tagged_column(&dumpPtr->ci)) {
        result = DumpColumn(interp, table, dumpPtr, col);
    }
    for (row = blt_table_first_tagged_row(&dumpPtr->ri); 
         (result) && (row != NULL); 
         row = blt_table_next_tagged_row(&dumpPtr->ri)) {
        result = DumpRow(interp, table, dumpPtr, row);
    }
    for (col = blt_table_first_tagged_column(&dumpPtr->ci); 
         (result) && (col != NULL); 
         col = blt_table_next_tagged_column(&dumpPtr->ci)) {
        for (row = blt_table_first_tagged_row(&dumpPtr->ri); 
             (result) && (row != NULL); 
             row = blt_table_next_tagged_row(&dumpPtr->ri)) {
            result = DumpValue(interp, table, dumpPtr, row, col);
        }
    }
    return (result) ? TCL_OK : TCL_ERROR;
}

static int
SetFreqArrVariable(Tcl_Interp *interp, Cmd *cmdPtr, size_t numRows,
                   FreqMap *freqMap, BLT_TABLE_COLUMN col,
                   SortSwitches *switchesPtr)
{
    size_t i;

    /* Return the new row order as a list. */
    for (i = 0; i < numRows; i++) {
        BLT_TABLE_ROW row;
        Tcl_Obj *valueObjPtr, *freqObjPtr, *objPtr;
        int isEmpty;
        
        row = freqMap[i].row;
        isEmpty = !blt_table_value_exists(cmdPtr->table, row, col);
        if ((isEmpty) && (switchesPtr->flags & SORT_NONEMPTY)) {
            continue;
        }
        if (isEmpty) {
            valueObjPtr = Tcl_NewStringObj(cmdPtr->emptyString, -1);
        } else {
            valueObjPtr = blt_table_get_obj(cmdPtr->table, row, col);
        }
        freqObjPtr = Tcl_NewWideIntObj(freqMap[i].count);
        objPtr = Tcl_ObjSetVar2(interp, switchesPtr->freqArrVarObjPtr, 
                                valueObjPtr, freqObjPtr, 0);
        if (objPtr == NULL) {
            return TCL_ERROR;
        }
    } 
    return TCL_OK;
}

static int
SetFreqListVariable(Tcl_Interp *interp, Cmd *cmdPtr, size_t numRows,
                    FreqMap *freqMap,
                    BLT_TABLE_COLUMN col, SortSwitches *switchesPtr)
{
    size_t i;
    Tcl_Obj *listObjPtr;

    /* Return the new row order as a list. */
    listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL);
    for (i = 0; i < numRows; i++) {
        Tcl_Obj *objPtr;
        
        objPtr = Tcl_NewWideIntObj(freqMap[i].count);
        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
    } 
    if (Tcl_ObjSetVar2(interp, switchesPtr->freqListVarObjPtr, NULL, listObjPtr,
                       0) == NULL) {
        return TCL_ERROR;
    }
    return TCL_OK;
}

static unsigned int sortFlags;

static int
CompareFrequencies(const void *a, const void *b)
{
    FreqMap *rowPtr1, *rowPtr2;
    int result;

    rowPtr1 = *(FreqMap **)a;
    rowPtr2 = *(FreqMap **)b;
    if (rowPtr1->count > rowPtr2->count) {
        result = -1;
    } else if (rowPtr2->count > rowPtr1->count) {
        result = 1;
    } else {
        result = 0;
    }
    return (sortFlags & TABLE_SORT_DECREASING) ? -result : result;
}

static int
SortFrequencies(Tcl_Interp *interp, Cmd *cmdPtr, size_t numRows, 
                BLT_TABLE_ROW *map, BLT_TABLE_COLUMN col, 
                SortSwitches *switchesPtr)
{
    BLT_TABLE_COMPARE_PROC *proc;
    FreqMap *freqMap; 
    size_t i, numEntries;
    
    proc = blt_table_get_compare_proc(cmdPtr->table, col, switchesPtr->tsFlags);
    freqMap = Blt_AssertCalloc(numRows, sizeof(FreqMap));
    numEntries = 0;

    /* Load the map with the frequencies of each run for a value. */
    for (i = 0; i < numRows; /*empty*/) {
        size_t j;
        size_t count;
        
        /* Find the next run of the same value as the current. */
        count = 1;
        for (j = i+1; j < numRows; j++) {
            if (((*proc)(cmdPtr->table, col, map[i], map[j])) != 0) {
                break;
            }
            count++;
        }
        if (switchesPtr->flags & SORT_UNIQUE) {
            /* Just one entry. Use the first row and count. */
            freqMap[numEntries].row = map[i];
            freqMap[numEntries].count = count;
            numEntries++;
            i = j;
        } else {
            /* An entry for each row. Replicate the count. */
            for (/*empty*/; i < j; i++) {
                freqMap[i].row = map[i];
                freqMap[i].count = count;
                numEntries++;
            }
        }
    }
    if (switchesPtr->flags & SORT_BYFREQ) {
        /* Sort the rows by their frequency. Set the global sortFlags
         * variable so that CompareFrequencies can pick it up. */
        sortFlags = switchesPtr->tsFlags;
        qsort(freqMap, numEntries, sizeof(FreqMap), CompareFrequencies);
    }
    if (switchesPtr->freqArrVarObjPtr != NULL) {
        /* Set the TCL array with the frequencies */
        if (SetFreqArrVariable(interp, cmdPtr, numEntries, freqMap, col,
                               switchesPtr) != TCL_OK) {
            return TCL_ERROR;
        }
    }
    if (switchesPtr->freqListVarObjPtr != NULL) {
        /* Set the TCL array with the list of frequencies. */
        if (SetFreqListVariable(interp, cmdPtr, numEntries, freqMap, col,
                                switchesPtr) != TCL_OK) {
            return TCL_ERROR;
        }
    }
    if (switchesPtr->flags & SORT_ALTER) {
        /* Overwrite the original sort map with the frequency map. */
        for (i = 0; i < numEntries; i++) {
            map[i] = freqMap[i].row;
        }
        /* Make row order permanent. */
        blt_table_set_row_map(cmdPtr->table, map);
    }
    Blt_Free(freqMap);
    return TCL_OK;
}

static int
SetSortedResult(Tcl_Interp *interp, Cmd *cmdPtr, size_t numRows, 
                BLT_TABLE_ROW *rows, BLT_TABLE_COLUMN col, 
                SortSwitches *switchesPtr)
{
    Tcl_Obj *listObjPtr;
    size_t i;
    
    listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL);
    for (i = 0; i < numRows; i++) {
        Tcl_Obj *objPtr;

        objPtr = NULL;
        switch(switchesPtr->returnType) {
        case SORT_RETURN_VALUES:
            objPtr = blt_table_get_obj(cmdPtr->table, rows[i], col);
            break;
        case SORT_RETURN_LABELS:
            objPtr = GetRowLabelObj(cmdPtr->table, rows[i]);
            break;
        case SORT_RETURN_INDICES:
            objPtr = GetRowIndexObj(cmdPtr->table, rows[i]);
            break;
        }
        if (objPtr != NULL) {
            Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
        }
    }
    Tcl_SetObjResult(interp, listObjPtr);
    return TCL_OK;
}

static int
SetUniqueSortedResult(Tcl_Interp *interp, Cmd *cmdPtr, size_t numRows, 
                      BLT_TABLE_ROW *rows, BLT_TABLE_COLUMN col, 
                      SortSwitches *switchesPtr)
{
    BLT_TABLE_COMPARE_PROC *proc;
    Tcl_Obj *listObjPtr;
    size_t i;
    

    /* Get the compare procedure for the column. We'll use that to sift out
     * unique values. */
    proc = blt_table_get_compare_proc(cmdPtr->table, col, switchesPtr->tsFlags);

    listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL);
    for (i = 0; i < numRows; /*empty*/) {
        size_t j;
        Tcl_Obj *objPtr;

        /* Find the next run of similar rows. */
        for (j = i + 1; j < numRows; j++) {
            if (((*proc)(cmdPtr->table, col, rows[i], rows[j])) != 0) {
                break;
            }
        }
        objPtr = NULL;
        switch(switchesPtr->returnType) {
        case SORT_RETURN_VALUES:
            objPtr = blt_table_get_obj(cmdPtr->table, rows[i], col);
            break;
        case SORT_RETURN_LABELS:
            objPtr = GetRowLabelObj(cmdPtr->table, rows[i]);
            break;
        case SORT_RETURN_INDICES:
            objPtr = GetRowIndexObj(cmdPtr->table, rows[i]);
            break;
        }
        if (objPtr != NULL) {
            Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
        }
        i = j;
    }
    Tcl_SetObjResult(interp, listObjPtr);
    return TCL_OK;
}

static const char *
GetTypeFromMode(int mode)
{
#ifdef WIN32
   if (mode == -1) {
       return "unknown";
   } else if (mode & FILE_ATTRIBUTE_DIRECTORY) {
        return "directory";
   } else if (mode &  FILE_ATTRIBUTE_HIDDEN) {
        return "hidden";
   } else if (mode &  FILE_ATTRIBUTE_READONLY) {
        return "readonly";
   } else {
       return "file";
   }
#else
    if (S_ISREG(mode)) {
        return "file";
    } else if (S_ISDIR(mode)) {
        return "directory";
    } else if (S_ISCHR(mode)) {
        return "characterSpecial";
    } else if (S_ISBLK(mode)) {
        return "blockSpecial";
    } else if (S_ISFIFO(mode)) {
        return "fifo";
#ifdef S_ISLNK
    } else if (S_ISLNK(mode)) {
        return "link";
#endif
#ifdef S_ISSOCK
    } else if (S_ISSOCK(mode)) {
        return "socket";
#endif
    }
    return "unknown";
#endif
}

INLINE static BLT_TABLE_COLUMN 
GetColumn(Tcl_Interp *interp, BLT_TABLE table, const char *label,
          BLT_TABLE_COLUMN_TYPE type)
{
    BLT_TABLE_COLUMN col;

    col = blt_table_get_column_by_label(table, label);
    if (col == NULL) {
        col = blt_table_create_column(interp, table, label);
        blt_table_set_column_type(interp, table, col, type);
    }
    return col;
}

static void
ExportToTable(Tcl_Interp *interp, BLT_TABLE table, const char *fileName, 
              Tcl_StatBuf *statPtr, ReadDirectory *switchesPtr)
{
    BLT_TABLE_ROW row;
    BLT_TABLE_COLUMN col;

    row = blt_table_create_row(interp, table, NULL);
    if (row == NULL) {
        return;
    }
    /* name */
    col = blt_table_get_column_by_label(table, "name");
    if (col == NULL) {
        col = blt_table_create_column(interp, table, "name");
    }
    blt_table_set_string(interp, table, row, col, fileName, -1);

    if (switchesPtr->mask & READ_DIR_TYPE) {
        col = GetColumn(interp, table, "type", TABLE_COLUMN_TYPE_STRING);
        blt_table_set_string(interp, table, row, col,
                             GetTypeFromMode(statPtr->st_mode), -1);
    }
    if (switchesPtr->mask & READ_DIR_SIZE) {
        col = GetColumn(interp, table, "size", TABLE_COLUMN_TYPE_LONG);
        blt_table_set_long(interp, table, row, col, statPtr->st_size);
    }
    if (switchesPtr->mask & READ_DIR_UID) {
        col = GetColumn(interp, table, "uid", TABLE_COLUMN_TYPE_LONG);
        blt_table_set_long(interp, table, row, col, statPtr->st_uid);
    }
    if (switchesPtr->mask & READ_DIR_GID) {
        col = GetColumn(interp, table, "gid", TABLE_COLUMN_TYPE_LONG);
        blt_table_set_long(interp, table, row, col, statPtr->st_gid);
    }
    if (switchesPtr->mask & READ_DIR_ATIME) {
        col = GetColumn(interp, table, "atime", TABLE_COLUMN_TYPE_LONG);
        blt_table_set_long(interp, table, row, col, statPtr->st_atime);
    }
    if (switchesPtr->mask & READ_DIR_MTIME) {
        col = GetColumn(interp, table, "mtime", TABLE_COLUMN_TYPE_LONG);
        blt_table_set_long(interp, table, row, col, statPtr->st_mtime);
    }
    if (switchesPtr->mask & READ_DIR_CTIME) {
        col = GetColumn(interp, table, "ctime", TABLE_COLUMN_TYPE_LONG);
        blt_table_set_long(interp, table, row, col, statPtr->st_ctime);
    }
    if (switchesPtr->mask & READ_DIR_MODE) {
        col = GetColumn(interp, table, "mode", TABLE_COLUMN_TYPE_LONG);
        blt_table_set_long(interp, table, row, col, statPtr->st_mode);
    }
    if (switchesPtr->mask & READ_DIR_PERMS) {
        col = GetColumn(interp, table, "perms", TABLE_COLUMN_TYPE_LONG);
        blt_table_set_long(interp, table, row, col, statPtr->st_mode & 07777);
    }
    if (switchesPtr->mask & READ_DIR_INO) {
        col = GetColumn(interp, table, "ino", TABLE_COLUMN_TYPE_LONG);
        blt_table_set_long(interp, table, row, col, statPtr->st_ino);
    }
    if (switchesPtr->mask & READ_DIR_NLINK) {
        col = GetColumn(interp, table, "nlink", TABLE_COLUMN_TYPE_LONG);
        blt_table_set_long(interp, table, row, col, statPtr->st_nlink);
    }
    if (switchesPtr->mask & READ_DIR_DEV) {
        col = GetColumn(interp, table, "dev", TABLE_COLUMN_TYPE_LONG);
        blt_table_set_long(interp, table, row, col, statPtr->st_rdev);
    }
}

/*
 *---------------------------------------------------------------------------
 *
 * AddOp --
 *
 *      Adds to rows from the source table onto the destination. If the
 *      destination table doesn't already have a column, one is
 *      automatically created.
 * 
 * Results:
 *      A standard TCL result. If the tag or column index is invalid,
 *      TCL_ERROR is returned and an error message is left in the
 *      interpreter result.
 *
 *      tableName add srcTableName ?switches?
 *
 *---------------------------------------------------------------------------
 */
static int
AddOp(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    AddSwitches switches;
    BLT_TABLE srcTable;
    size_t oldLength, count;
    int result;
    BLT_TABLE_COLUMN srcCol;

    /* Process switches following the column names. */
    if (blt_table_open(interp, Tcl_GetString(objv[2]), &srcTable) != TCL_OK) {
        return TCL_ERROR;
    }
    switches.flags = 0;
    result = TCL_ERROR;
    rowIterSwitch.clientData = srcTable;
    columnIterSwitch.clientData = srcTable;
    blt_table_iterate_all_rows(srcTable, &switches.ri);
    blt_table_iterate_all_columns(srcTable, &switches.ci);
    if (Blt_ParseSwitches(interp, addSwitches, objc - 3, objv + 3, 
        &switches, BLT_SWITCH_DEFAULTS) < 0) {
        goto error;
    }
    oldLength = blt_table_num_rows(cmdPtr->table);
    count = switches.ri.numEntries;
    if (blt_table_extend_rows(interp, cmdPtr->table, count, NULL) != TCL_OK) {
        goto error;
    }
    for (srcCol = blt_table_first_tagged_column(&switches.ci); srcCol != NULL; 
         srcCol = blt_table_next_tagged_column(&switches.ci)) {
        const char *label;
        BLT_TABLE_COLUMN dstCol;
        BLT_TABLE_ROW srcRow;
        size_t i;

        label = blt_table_column_label(srcCol);
        dstCol = blt_table_get_column_by_label(cmdPtr->table, label);
        if (dstCol == NULL) {
            /* If column doesn't exist in destination table, create a new
             * column, copying the label and the column type. */
            if (blt_table_extend_columns(interp, cmdPtr->table, 1, &dstCol) 
                != TCL_OK) {
                goto error;
            }
            if (blt_table_set_column_label(interp, cmdPtr->table, dstCol, label)
                != TCL_OK) {
                goto error;
            }
            if (blt_table_set_column_type(interp, cmdPtr->table, dstCol, 
                blt_table_column_type(srcCol)) != TCL_OK) {
                goto error;
            }
        }
        i = oldLength;
        for (srcRow = blt_table_first_tagged_row(&switches.ri); srcRow != NULL; 
             srcRow = blt_table_next_tagged_row(&switches.ri)) {
            BLT_TABLE_VALUE value;
            BLT_TABLE_ROW dstRow;

            value = blt_table_get_value(srcTable, srcRow, srcCol);
            if (value == NULL) {
                continue;
            }
            dstRow = blt_table_row(cmdPtr->table, i);
            if (blt_table_set_value(cmdPtr->table, dstRow, dstCol, value) 
                != TCL_OK) {
                goto error;
            }
            i++;
        }
        if ((switches.flags & COPY_NOTAGS) == 0) {
            CopyColumnTags(srcTable, cmdPtr->table, srcCol, dstCol);
        }
    }
    result = TCL_OK;
 error:
    blt_table_close(srcTable);
    Blt_FreeSwitches(addSwitches, &switches, 0);
    return result;
}

/*
 *---------------------------------------------------------------------------
 *
 * AppendOp --
 *
 *      Appends one or more values to the current value at the given
 *      location. If the column or row doesn't already exist, it will
 *      automatically be created.
 * 
 * Results:
 *      A standard TCL result. If the tag or index is invalid, TCL_ERROR is
 *      returned and an error message is left in the interpreter result.
 *      
 *      tableName append rowName colName ?value ...?
 *
 *---------------------------------------------------------------------------
 */
static int
AppendOp(ClientData clientData, Tcl_Interp *interp, int objc,
         Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    BLT_TABLE table;
    BLT_TABLE_ITERATOR ri, ci;
    BLT_TABLE_COLUMN col;
    int i, extra;

    table = cmdPtr->table;
    if (IterateRowsWithCreate(interp, table, objv[2], &ri) != TCL_OK) {
        return TCL_ERROR;
    }
    if (IterateColumnsWithCreate(interp, table, objv[3], &ci) != TCL_OK) {
        return TCL_ERROR;
    }
    extra = 0;
    for (i = 4; i < objc; i++) {
        int length;

        Tcl_GetStringFromObj(objv[i], &length);
        extra += length;
    }
    if (extra == 0) {
        return TCL_OK;
    }
    for (col = blt_table_first_tagged_column(&ci); col != NULL; 
         col = blt_table_next_tagged_column(&ci)) {
        BLT_TABLE_ROW row;
        
        for (row = blt_table_first_tagged_row(&ri); row != NULL; 
             row = blt_table_next_tagged_row(&ri)) {
            int i;

            for (i = 4; i < objc; i++) {
                const char *s;
                int length;
                
                s = Tcl_GetStringFromObj(objv[i], &length);
                if (blt_table_append_string(interp, table, row, col, s, length) 
                    != TCL_OK) {
                    return TCL_ERROR;
                }
            }
        }
    }       
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * AttachOp --
 *
 *      tableName attach newTableName
 *
 *---------------------------------------------------------------------------
 */
static int
AttachOp(ClientData clientData, Tcl_Interp *interp, int objc,
         Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;

    if (objc == 3) {
        const char *qualName;
        Blt_ObjectName objName;
        BLT_TABLE table;
        Tcl_DString ds;
        int result;

        if (!Blt_ParseObjectName(interp, Tcl_GetString(objv[2]), &objName, 0)) {
            return TCL_ERROR;
        }
        qualName = Blt_MakeQualifiedName(&objName, &ds);
        result = blt_table_open(interp, qualName, &table);
        Tcl_DStringFree(&ds);
        if (result != TCL_OK) {
            return TCL_ERROR;
        }
        if (cmdPtr->table != NULL) {
            Blt_HashEntry *hPtr;
            Blt_HashSearch iter;
            
            blt_table_close(cmdPtr->table);

            /* Free the extra bookkeeping that we're maintaining about the
             * current table (table traces and notifiers).  */
            for (hPtr = Blt_FirstHashEntry(&cmdPtr->traceTable, &iter); 
                 hPtr != NULL; hPtr = Blt_NextHashEntry(&iter)) {
                TraceInfo *tracePtr;

                tracePtr = Blt_GetHashValue(hPtr);
                blt_table_delete_trace(cmdPtr->table, tracePtr->trace);
            }
            Blt_DeleteHashTable(&cmdPtr->traceTable);
            Blt_InitHashTable(&cmdPtr->traceTable, TCL_STRING_KEYS);
            for (hPtr = Blt_FirstHashEntry(&cmdPtr->watchTable, &iter); 
                hPtr != NULL; hPtr = Blt_NextHashEntry(&iter)) {
                WatchInfo *watchPtr;

                watchPtr = Blt_GetHashValue(hPtr);
                FreeWatchInfo(watchPtr);
            }
            Blt_DeleteHashTable(&cmdPtr->watchTable);
            Blt_InitHashTable(&cmdPtr->watchTable, TCL_STRING_KEYS);
        }
        cmdPtr->table = table;
    }
    Tcl_SetStringObj(Tcl_GetObjResult(interp), 
        blt_table_name(cmdPtr->table), -1);
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * ClearOp --
 *
 *      tableName clear
 *
 *---------------------------------------------------------------------------
 */
static int
ClearOp(ClientData clientData, Tcl_Interp *interp, int objc,
        Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;

    blt_table_clear(cmdPtr->table);
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * ColumnCopyOp --
 *
 *      Copies the specified columns to the table.  A different table may
 *      be selected as the source.
 * 
 * Results:
 *      A standard TCL result. If the tag or column index is invalid,
 *      TCL_ERROR is returned and an error message is left in the
 *      interpreter result.
 *
 *      tableName column copy destColumn srcColumn ?switches?
 *
 *---------------------------------------------------------------------------
 */
static int
ColumnCopyOp(ClientData clientData, Tcl_Interp *interp, int objc,
             Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    BLT_TABLE srcTable, destTable;
    BLT_TABLE_COLUMN srcColumn, destColumn;
    CopySwitches switches;
    int result;

    /* Process switches following the column names. */
    memset(&switches, 0, sizeof(switches));
    result = TCL_ERROR;
    if (Blt_ParseSwitches(interp, copySwitches, objc - 5, objv + 5, &switches, 
        BLT_SWITCH_DEFAULTS) < 0) {
        goto error;
    }
    /* Need to get the -table switch first to know what table to look up
     * for the source column. */
    srcTable = destTable = cmdPtr->table;
    if (switches.table != NULL) {
        srcTable = switches.table;
    }
    /* Destination column may not already exist. */
    destColumn = NULL;
    if ((switches.flags & COPY_NEW) == 0) {
        destColumn = blt_table_get_column(interp, destTable, objv[3]);
    }
    if (destColumn == NULL) {
        destColumn = blt_table_create_column(interp, destTable,
                                             Tcl_GetString(objv[3]));
        if (destColumn == NULL) {
            goto error;
        }
    }
    /* Source column must exist. */
    srcColumn = blt_table_get_column(interp, srcTable, objv[4]);
    if (srcColumn == NULL) {
        goto error;
    }
    if (switches.flags & COPY_APPEND) {
        if (AppendColumn(interp, srcTable, destTable, srcColumn, destColumn)
            != TCL_OK) {
            goto error;
        }
    } else {
        if (CopyColumn(interp, srcTable, destTable, srcColumn, destColumn)
            != TCL_OK) {
            goto error;
        }
    }
    if ((switches.flags & COPY_NOTAGS) == 0) {
        CopyColumnTags(srcTable, destTable, srcColumn, destColumn);
    }
    result = TCL_OK;
 error:
    Blt_FreeSwitches(copySwitches, &switches, 0);
    return result;
    
}

/*
 *---------------------------------------------------------------------------
 *
 * ColumnJoinOp --
 *
 *      Joins by column the source table onto the destination.  Duplicate
 *      column labels are allowed.
 * 
 * Results:
 *      A standard TCL result. If the tag or column index is invalid,
 *      TCL_ERROR is returned and an error message is left in the
 *      interpreter result.
 *
 *      tableName column join srcTableName ?switches?
 *
 *---------------------------------------------------------------------------
 */
static int
ColumnJoinOp(ClientData clientData, Tcl_Interp *interp, int objc,
             Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    JoinSwitches switches;
    BLT_TABLE src, dst;
    size_t oldWidth, extra;
    int result;
    BLT_TABLE_COLUMN srcCol;
    size_t i;

    /* Process switches following the column names. */
    if (blt_table_open(interp, Tcl_GetString(objv[3]), &src) != TCL_OK) {
        return TCL_ERROR;
    }
    switches.flags = 0;
    result = TCL_ERROR;
    dst = cmdPtr->table;
    columnIterSwitch.clientData = src;
    blt_table_iterate_all_columns(src, &switches.ci);
    if (Blt_ParseSwitches(interp, joinSwitches, objc - 4, objv + 4, &switches, 
        BLT_SWITCH_DEFAULTS | JOIN_COLUMN) < 0) {
        goto error;
    }
    oldWidth = blt_table_num_columns(dst);
    extra = switches.ci.numEntries;
    if (blt_table_extend_columns(interp, dst, extra, NULL) != TCL_OK) {
        goto error;
    }
    i = oldWidth;
    for (srcCol = blt_table_first_tagged_column(&switches.ci); srcCol != NULL; 
         srcCol = blt_table_next_tagged_column(&switches.ci)) {
        const char *label;
        BLT_TABLE_COLUMN dstCol;
        BLT_TABLE_ROW srcRow;
        int srcType;

        /* Copy the label and the column type. */
        label = blt_table_column_label(srcCol);
        dstCol = blt_table_column(dst, i);
        i++;
        if (blt_table_set_column_label(interp, dst, dstCol, label) != TCL_OK) {
            goto error;
        }
        srcType = blt_table_column_type(srcCol);
        if (blt_table_set_column_type(interp, cmdPtr->table, dstCol, srcType)
            != TCL_OK) {
            goto error;
        }
        for (srcRow = blt_table_first_row(src); srcRow != NULL; 
             srcRow = blt_table_next_row(srcRow)) {
            BLT_TABLE_VALUE value;
            BLT_TABLE_ROW dstRow;

            dstRow = blt_table_get_row_by_label(dst, label);
            if (dstRow == NULL) {

                /* If row doesn't exist in destination table, create a new
                 * row, copying the label. */
                
                if (blt_table_extend_columns(interp, dst, 1, &dstCol)
                    !=TCL_OK) {
                    goto error;
                }
                if (blt_table_set_row_label(interp, dst, dstRow, label) 
                    != TCL_OK) {
                    goto error;
                }
            }
            value = blt_table_get_value(src, srcRow, srcCol);
            if (value == NULL) {
                continue;
            }
            if (blt_table_set_value(dst, dstRow, dstCol, value) != TCL_OK) {
                goto error;
            }
        }
        if ((switches.flags & COPY_NOTAGS) == 0) {
            CopyColumnTags(src, dst, srcCol, dstCol);
        }
    }
    result = TCL_OK;
 error:
    blt_table_close(src);
    Blt_FreeSwitches(addSwitches, &switches, JOIN_COLUMN);
    return result;
}

/*
 *---------------------------------------------------------------------------
 *
 * ColumnDeleteOp --
 *
 *      Deletes the columns designated.  One or more columns may be deleted
 *      using a tag.
 * 
 * Results:
 *      A standard TCL result. If the tag or column index is invalid,
 *      TCL_ERROR is returned and an error message is left in the
 *      interpreter result.
 *
 *      tableName column delete ?colName ...?
 *
 *---------------------------------------------------------------------------
 */
static int
ColumnDeleteOp(ClientData clientData, Tcl_Interp *interp, int objc,
               Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    BLT_TABLE_ITERATOR ci;
    BLT_TABLE_COLUMN col;
    int result;

    result = TCL_ERROR;
    if (blt_table_iterate_columns_objv(interp, cmdPtr->table, objc - 3,
                objv + 3, &ci) != TCL_OK) {
        return TCL_ERROR;
    }
    /* 
     * Walk through the list of column offsets, deleting each column.
     */
    for (col = blt_table_first_tagged_column(&ci); col != NULL; 
         col = blt_table_next_tagged_column(&ci)) {
        if (blt_table_delete_column(cmdPtr->table, col) != TCL_OK) {
            goto error;
        }
    }
    result = TCL_OK;
 error:
    blt_table_free_iterator_objv(&ci);
    return result;
}

/*
 *---------------------------------------------------------------------------
 *
 * ColumnDupOp --
 *
 *      Duplicates the specified columns in the table.  This differs from
 *      ColumnCopyOp, since the same table is the source and destination.
 * 
 * Results:
 *      A standard TCL result. If the tag or column index is invalid,
 *      TCL_ERROR is returned and an error message is left in the interpreter
 *      result.
 *
 *      tableName column dup ?colName ...?
 *
 *---------------------------------------------------------------------------
 */
static int
ColumnDupOp(ClientData clientData, Tcl_Interp *interp, int objc,
            Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    BLT_TABLE table;
    Tcl_Obj *listObjPtr;
    BLT_TABLE_ITERATOR ci;
    BLT_TABLE_COLUMN srcCol;

    table = cmdPtr->table;
    listObjPtr = NULL;
    if (blt_table_iterate_columns_objv(interp, table, objc - 3, objv + 3, &ci) 
        != TCL_OK) {
        goto error;
    }
    listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL);
    for (srcCol = blt_table_first_tagged_column(&ci); srcCol != NULL; 
         srcCol = blt_table_next_tagged_column(&ci)) {
        size_t i;
        BLT_TABLE_COLUMN dstCol;

        dstCol = blt_table_create_column(interp, table, 
                blt_table_column_label(srcCol));
        if (dstCol == NULL) {
            goto error;
        }
        if (CopyColumn(interp, table, table, srcCol, dstCol) != TCL_OK) {
            goto error;
        }
        CopyColumnTags(table, table, srcCol, dstCol);
        i = blt_table_column_index(table, dstCol);
        Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewWideIntObj(i));
    }
    blt_table_free_iterator_objv(&ci);
    Tcl_SetObjResult(interp, listObjPtr);
    return TCL_OK;
 error:
    blt_table_free_iterator_objv(&ci);
    if (listObjPtr != NULL) {
        Tcl_DecrRefCount(listObjPtr);
    }
    return TCL_ERROR;
}

/*
 *---------------------------------------------------------------------------
 *
 * ColumnEmptyOp --
 *
 *      Returns a list of the rows with empty values in the given column.
 *
 * Results:
 *      A standard TCL result. If the tag or column index is invalid,
 *      TCL_ERROR is returned and an error message is left in the
 *      interpreter result.
 *
 *      tableName column empty colName
 *      
 *---------------------------------------------------------------------------
 */
static int
ColumnEmptyOp(ClientData clientData, Tcl_Interp *interp, int objc,
              Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    BLT_TABLE_COLUMN col;
    Tcl_Obj *listObjPtr;

    BLT_TABLE_ROW row;

    col = blt_table_get_column(interp, cmdPtr->table, objv[3]);
    if (col == NULL) {
        return TCL_ERROR;
    }
    listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL);
    for (row = blt_table_first_row(cmdPtr->table); row != NULL;
         row = blt_table_next_row(row)) {
        if (!blt_table_value_exists(cmdPtr->table, row, col))  {
            Tcl_Obj *objPtr;
            
            objPtr = GetRowIndexObj(cmdPtr->table, row);
            Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
        }
    }
    Tcl_SetObjResult(interp, listObjPtr);
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * ColumnExistsOp --
 *
 *      Indicates is the given column exists.  The column description can
 *      be either an index, label, or single tag.
 *
 *      Problem: The blt_table_iterate_columns function checks both for 1)
 *               valid/invalid indices, labels, and tags and 2) syntax
 *               errors.
 * 
 * Results:
 *      A standard TCL result. If the tag or column index is invalid,
 *      TCL_ERROR is returned and an error message is left in the interpreter
 *      result.
 *
 *      tableName column exists colName
 *      
 *---------------------------------------------------------------------------
 */
static int
ColumnExistsOp(ClientData clientData, Tcl_Interp *interp, int objc,
               Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    BLT_TABLE_COLUMN col;
    int bool;

    col = blt_table_get_column(NULL, cmdPtr->table, objv[3]);
    bool = (col != NULL);
    Tcl_SetBooleanObj(Tcl_GetObjResult(interp), bool);
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * ColumnExtendOp --
 *
 *      Extends the table by the given number of columns.
 * 
 * Results:
 *      A standard TCL result. If the tag or column index is invalid,
 *      TCL_ERROR is returned and an error message is left in the interpreter
 *      result.
 *
 *      tableName column extend numColumns ?switches?
 *      
 *---------------------------------------------------------------------------
 */
static int
ColumnExtendOp(ClientData clientData, Tcl_Interp *interp, int objc,
               Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    BLT_TABLE table;
    BLT_TABLE_COLUMN *cols;
    long numColumns;
    ExtendSwitches switches;
    int result;
    
    memset(&switches, 0, sizeof(switches));
    if (Blt_ParseSwitches(interp, extendSwitches, objc - 4, objv + 4,
                &switches, BLT_SWITCH_DEFAULTS) < 0) {
        return TCL_ERROR;
    }
    table = cmdPtr->table;

    if (Blt_GetCountFromObj(interp, objv[3], COUNT_NNEG, &numColumns)
        != TCL_OK) {
        return TCL_ERROR;
    }
    if (numColumns == 0) {
        return TCL_OK;
    }
    cols = Blt_AssertMalloc(numColumns * sizeof(BLT_TABLE_COLUMN));
    result = blt_table_extend_columns(interp, table, numColumns, cols);
    if ((result == TCL_OK) && (switches.labels != NULL)) {
        size_t i;
        const char **p;
        
        for (i = 0, p = switches.labels; *p != NULL; p++, i++) {
            result = blt_table_set_column_label(interp, table, cols[i], *p);
            if (result != TCL_OK) {
                break;
            }
        }
    }
    if (result == TCL_OK) {
        size_t i;
        Tcl_Obj *listObjPtr;

        listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL);
        for (i = 0; i < numColumns; i++) {
            Tcl_Obj *objPtr;
            
            objPtr = GetColumnIndexObj(table, cols[i]);
            Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
        }
        Tcl_SetObjResult(interp, listObjPtr);
    }
    Blt_Free(cols);
    Blt_FreeSwitches(extendSwitches, &switches, 0);
    return result;
}

/*
 *---------------------------------------------------------------------------
 *
 * ColumnGetOp --
 *
 *      Retrieves a column of values.  The column argument can be either a
 *      tag, label, or column index.  If it is a tag, it must refer to
 *      exactly one column.  If row arguments exist they must refer to
 *      label or row.  We always return the row label.
 * 
 * Results:
 *      A standard TCL result.  If successful, a list of values is returned in
 *      the interpreter result.  If the column index is invalid, TCL_ERROR is
 *      returned and an error message is left in the interpreter result.
 *      
 *      tableName column get -labels colName ?rowName ...? 
 *
 *---------------------------------------------------------------------------
 */
static int
ColumnGetOp(ClientData clientData, Tcl_Interp *interp, int objc,
            Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    BLT_TABLE table;
    BLT_TABLE_COLUMN col;
    Tcl_Obj *listObjPtr;
    const char *string;
    int needLabels;

    string = Tcl_GetString(objv[3]);
    needLabels = FALSE;
    if (strcmp(string, "-labels") == 0) {
        objv++, objc--;
        needLabels = TRUE;
    }
    table = cmdPtr->table;
    col = blt_table_get_column(interp, cmdPtr->table, objv[3]);
    if (col == NULL) {
        return TCL_ERROR;
    }
    listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL);
    if (objc == 4) {
        BLT_TABLE_ROW row;

        for (row = blt_table_first_row(cmdPtr->table); row != NULL;
             row = blt_table_next_row(row)) {
            Tcl_Obj *objPtr;

            if (needLabels) {
                objPtr = Tcl_NewStringObj(blt_table_row_label(row), -1);
            } else {
                objPtr = GetRowIndexObj(cmdPtr->table, row);
            }
            Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
            objPtr = blt_table_get_obj(cmdPtr->table, row, col);
            if (objPtr == NULL) {
                objPtr = Tcl_NewStringObj(cmdPtr->emptyString, -1);
            }
            Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
        }
    } else {
        BLT_TABLE_ITERATOR ri;
        BLT_TABLE_ROW row;

        if (blt_table_iterate_rows_objv(interp, table, objc - 4, objv + 4, &ri) 
            != TCL_OK) {
            return TCL_ERROR;
        }
        for (row = blt_table_first_tagged_row(&ri); row != NULL; 
             row = blt_table_next_tagged_row(&ri)) {
            Tcl_Obj *objPtr;
            
            if (needLabels) {
                objPtr = Tcl_NewStringObj(blt_table_row_label(row), -1);
            } else {
                objPtr = GetRowIndexObj(cmdPtr->table, row);
            }
            Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
            objPtr = blt_table_get_obj(cmdPtr->table, row, col);
            if (objPtr == NULL) {
                objPtr = Tcl_NewStringObj(cmdPtr->emptyString, -1);
            }
            Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
        }
        blt_table_free_iterator_objv(&ri);
    }
    Tcl_SetObjResult(interp, listObjPtr);
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * ColumnIndexOp --
 *
 *      Returns the column index of the given column tag, label, or index.
 *      A tag can't represent more than one column.
 * 
 * Results:
 *      A standard TCL result. If the tag or column index is invalid,
 *      TCL_ERROR is returned and an error message is left in the
 *      interpreter result.
 *
 *      tableName column index colName
 *      
 *---------------------------------------------------------------------------
 */
static int
ColumnIndexOp(ClientData clientData, Tcl_Interp *interp, int objc,
              Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    BLT_TABLE_ITERATOR iter;
    long index;

    index = -1;
    if (blt_table_iterate_columns(interp, cmdPtr->table, objv[3], &iter)
        == TCL_OK) {
        BLT_TABLE_COLUMN col;

        col = blt_table_first_tagged_column(&iter);
        if (col != NULL) {
            BLT_TABLE_COLUMN next;
            
            index = blt_table_column_index(cmdPtr->table, col);
            next = blt_table_next_tagged_column(&iter);
            if (next != NULL) {
                /* It's not an error to look for an index of a column that
                 * doesn't exist. Duplicate labels are another story. This
                 * is too subtle a problem. Better to error on
                 * duplicates.  */
                const char *tag;
            
                blt_table_column_spec(cmdPtr->table, objv[3], &tag);
                Tcl_AppendResult(interp, "multiple columns specified by \"", 
                                 tag, "\"", (char *)NULL);
                return TCL_ERROR;
            }
        }
    }
    Tcl_SetWideIntObj(Tcl_GetObjResult(interp), index);
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * ColumnIndicesOp --
 *
 *      Returns a list of indices for the given column labels.  If the
 *      column by that label doesn't exists, -1 is returned for the index.
 *      If the column label refers to multiple columns, a list of the
 *      indices is returned.
 * 
 * Results:
 *      A standard TCL result. If the tag or column index is invalid,
 *      TCL_ERROR is returned and an error message is left in the
 *      interpreter result.
 *
 *      tableName column indices ?label ...?
 *      
 *---------------------------------------------------------------------------
 */
static int
ColumnIndicesOp(ClientData clientData, Tcl_Interp *interp, int objc,
                Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    Tcl_Obj *listObjPtr;
    IndicesSwitches switches;
    int i;

    switches.flags = 0;
    i = Blt_ParseSwitches(interp, indicesSwitches, objc - 3, objv + 3, 
        &switches, BLT_SWITCH_OBJV_PARTIAL);
    if (i < 0)  {
        return TCL_ERROR;
    }
    objc -= i, objv += i;
    listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL);
    for (i = 3; i < objc; i++) {
        Blt_HashTable *tablePtr;
        Blt_HashEntry *hPtr;
        Blt_HashSearch iter;
        const char *label;
        
        label = Tcl_GetString(objv[i]);
        tablePtr = blt_table_column_get_label_table(cmdPtr->table, label);
        if (tablePtr == NULL) {
            /* Label doesn't refer to any column. */
            Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewWideIntObj(-1));
            continue;
        }
        if ((switches.flags & INDICES_DUPLICATES) &&
            (tablePtr->numEntries == 1)) {
            continue;                   /* Only report duplicates. */
        }
        if (objc == 4) {
            /* Check if there's only one label argument.  If this case we
             * don't want to return a list of lists for duplicate labels.*/
            for (hPtr = Blt_FirstHashEntry(tablePtr, &iter); hPtr != NULL;
                 hPtr = Blt_NextHashEntry(&iter)) {
                BLT_TABLE_COLUMN col;
                Tcl_Obj *objPtr;
                
                col = Blt_GetHashValue(hPtr);
                objPtr = GetColumnIndexObj(cmdPtr->table, col);
                Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
            }
        } else {
            Tcl_Obj *subListObjPtr;

            subListObjPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL);
            for (hPtr = Blt_FirstHashEntry(tablePtr, &iter); hPtr != NULL;
                 hPtr = Blt_NextHashEntry(&iter)) {
                BLT_TABLE_COLUMN col;
                Tcl_Obj *objPtr;
                
                col = Blt_GetHashValue(hPtr);
                objPtr = GetColumnIndexObj(cmdPtr->table, col);
                Tcl_ListObjAppendElement(interp, subListObjPtr, objPtr);
            }
            Tcl_ListObjAppendElement(interp, listObjPtr, subListObjPtr);
        }
    }
    Tcl_SetObjResult(interp, listObjPtr);
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * ColumnCreateOp --
 *
 *      Creates a single new column in the table.  The location of the new
 *      column may be specified by -before or -after switches.  By default
 *      the new column is added to the end of the table.
 * 
 * Results:
 *      A standard TCL result. If the tag or column index is invalid,
 *      TCL_ERROR is returned and an error message is left in the
 *      interpreter result.
 *
 *      tableName column create ?switches?
 *      
 *---------------------------------------------------------------------------
 */
static int
ColumnCreateOp(ClientData clientData, Tcl_Interp *interp, int objc,
               Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    InsertColumnSwitches switches;
    BLT_TABLE_COLUMN col;

    memset(&switches, 0, sizeof(switches));
    switches.cmdPtr = cmdPtr;
    switches.type = TABLE_COLUMN_TYPE_STRING;

    if (Blt_ParseSwitches(interp, insertColumnSwitches, objc - 3, objv + 3, 
        &switches, 0) < 0) {
        goto error;
    }
    col = blt_table_create_column(interp, cmdPtr->table, switches.label);
    if (col == NULL) {
        goto error;
    }
    if (blt_table_set_column_type(interp, cmdPtr->table, col, switches.type)
        != TCL_OK) {
        goto error;
    }
    if (switches.destColumn != NULL) {
        if (blt_table_move_columns(interp, cmdPtr->table, switches.destColumn, 
                col, col, switches.flags & INSERT_AFTER) != TCL_OK) {
            goto error;
        }
    }
    if (switches.tags != NULL) {
        Tcl_Obj **elv;
        int elc;
        int i;

        if (Tcl_ListObjGetElements(interp, switches.tags, &elc, &elv) 
            != TCL_OK) {
            goto error;
        }
        for (i = 0; i < elc; i++) {
            if (blt_table_set_column_tag(interp, cmdPtr->table, col, 
                        Tcl_GetString(elv[i])) != TCL_OK) {
                goto error;
            }
        }
    }
    Tcl_SetObjResult(interp, GetColumnIndexObj(cmdPtr->table, col));
    Blt_FreeSwitches(insertColumnSwitches, &switches, 0);
    return TCL_OK;
 error:
    Blt_FreeSwitches(insertColumnSwitches, &switches, 0);
    return TCL_ERROR;
}

/*
 *---------------------------------------------------------------------------
 *
 * ColumnLabelOp --
 *
 *      Gets/sets one or more column labels.  
 * 
 * Results:
 *      A standard TCL result.  If successful, the old column label is
 *      returned in the interpreter result.  If the column index is invalid,
 *      TCL_ERROR is returned and an error message is left in the interpreter
 *      result.
 *      
 *      tableName column label colName ?label colName label ...? 
 *
 *---------------------------------------------------------------------------
 */
static int
ColumnLabelOp(ClientData clientData, Tcl_Interp *interp, int objc,
              Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    BLT_TABLE table;

    table = cmdPtr->table;
    if (objc == 4) {
        const char *label;
        BLT_TABLE_COLUMN col;

        col = blt_table_get_column(interp, table, objv[3]);
        if (col == NULL) {
            return TCL_ERROR;
        }
        label = blt_table_column_label(col);
        Tcl_SetStringObj(Tcl_GetObjResult(interp), label, -1);
    }  else {
        int i;
        
        if ((objc - 3) & 1) {
            Tcl_AppendResult(interp,"odd # of column/label pairs: should be \"",
                Tcl_GetString(objv[0]), " column label ?column label ...?", 
                             (char *)NULL);
            return TCL_ERROR;
        }
        for (i = 3; i < objc; i += 2) {
            BLT_TABLE_COLUMN col;
            const char *label;

            col = blt_table_get_column(interp, table, objv[i]);
            if (col == NULL) {
                return TCL_ERROR;
            }
            label = Tcl_GetString(objv[i+1]);
            if (label[0] == '\0') {
                continue;               /* Don't set empty labels. */
            }
            if (blt_table_set_column_label(interp, table, col, label) != TCL_OK) {
                return TCL_ERROR;
            }
        }
    }
    return TCL_OK;
}


/*
 *---------------------------------------------------------------------------
 *
 * ColumnLabelsOp --
 *
 *      Gets/sets all the column labels in the table.  
 * 
 * Results:
 *      A standard TCL result.  If successful, a list of values is returned
 *      in the interpreter result.
 *      
 *      tableName column labels ?labelList? 
 *
 *---------------------------------------------------------------------------
 */
static int
ColumnLabelsOp(ClientData clientData, Tcl_Interp *interp, int objc,
               Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    BLT_TABLE table;

    table = cmdPtr->table;
    if (objc == 3) {
        BLT_TABLE_COLUMN col;
        Tcl_Obj *listObjPtr;

        listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL);
        for (col = blt_table_first_column(cmdPtr->table); col != NULL;
             col = blt_table_next_column(col)) {
            const char *label;
            Tcl_Obj *objPtr;
            
            label = blt_table_column_label(col);
            objPtr = Tcl_NewStringObj(label, -1);
            Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
        }
        Tcl_SetObjResult(interp, listObjPtr);
    } else {
        Tcl_Obj **elv;
        int elc, n;
        int i;

        if (Tcl_ListObjGetElements(interp, objv[3], &elc, &elv) != TCL_OK) {
            return TCL_ERROR;
        }
        n = MIN(elc, blt_table_num_columns(table));
        for (i = 0; i < n; i++) {
            BLT_TABLE_COLUMN col;
            const char *label;

            col = blt_table_column(table, i);
            label = Tcl_GetString(elv[i]);
            if (label[0] == '\0') {
                continue;
            }
            if (blt_table_set_column_label(interp, table, col, label)
                != TCL_OK) {
                return TCL_ERROR;
            }
        }
    }
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * ColumnMoveOp --
 *
 *      Moves the given number of columns to another location in the table.
 * 
 * Results:
 *      A standard TCL result. If the column index is invalid, TCL_ERROR is
 *      returned and an error message is left in the interpreter result.
 *      
 *      tableName column move destColumn firstColumn lastColumn ?switches...?
 *
 *---------------------------------------------------------------------------
 */
static int
ColumnMoveOp(ClientData clientData, Tcl_Interp *interp, int objc,
             Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    MoveSwitches switches;
    BLT_TABLE_COLUMN destColumn, firstColumn, lastColumn;

    destColumn = blt_table_get_column(interp, cmdPtr->table, objv[3]);
    if (destColumn == NULL) {
        return TCL_ERROR;
    }
    firstColumn = blt_table_get_column(interp, cmdPtr->table, objv[4]);
    if (firstColumn == NULL) {
        return TCL_ERROR;
    }
    lastColumn = blt_table_get_column(interp, cmdPtr->table, objv[5]);
    if (lastColumn == NULL) {
        return TCL_ERROR;
    }
    /* Check if range is valid. */
    if ((blt_table_column_index(cmdPtr->table, firstColumn) > 
         blt_table_column_index(cmdPtr->table, lastColumn))) {
        return TCL_OK;                  /* No range. */
    }

    /* Check that destination is outside the range of columns to be
       moved. */
    if ((blt_table_column_index(cmdPtr->table, destColumn) >= 
         blt_table_column_index(cmdPtr->table, firstColumn)) &&
        (blt_table_column_index(cmdPtr->table, destColumn) <= 
         blt_table_column_index(cmdPtr->table, lastColumn))) {
        Tcl_AppendResult(interp, "destination column \"", 
                Tcl_GetString(objv[3]),
                 "\" can't be in the range of columns to be moved", 
                (char *)NULL);
        return TCL_ERROR;
    }
    memset(&switches, 0, sizeof(switches));
    if (Blt_ParseSwitches(interp, moveSwitches, objc - 6, objv + 6, 
        &switches, BLT_SWITCH_DEFAULTS) < 0) {
        return TCL_ERROR;
    }
    return blt_table_move_columns(interp, cmdPtr->table, destColumn, 
          firstColumn, lastColumn, switches.flags & MOVE_AFTER);
}

/*
 *---------------------------------------------------------------------------
 *
 * ColumnNamesOp --
 *
 *      Reports the labels of all columns.  
 * 
 * Results:
 *      Always returns TCL_OK.  The interpreter result is a list of column
 *      labels.
 *      
 *      tableName column names ?pattern ...?
 *
 *---------------------------------------------------------------------------
 */
static int
ColumnNamesOp(ClientData clientData, Tcl_Interp *interp, int objc,
              Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    Tcl_Obj *listObjPtr;
    BLT_TABLE_COLUMN col;

    listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL);
    for (col = blt_table_first_column(cmdPtr->table); col != NULL;
         col = blt_table_next_column(col)) {
        const char *label;
        int match;
        int i;

        label = blt_table_column_label(col);
        match = (objc == 3);
        for (i = 3; i < objc; i++) {
            char *pattern;

            pattern = Tcl_GetString(objv[i]);
            if (Tcl_StringMatch(label, pattern)) {
                match = TRUE;
                break;
            }
        }
        if (match) {
            Tcl_Obj *objPtr;

            objPtr = Tcl_NewStringObj(label, -1);
            Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
        }
    }
    Tcl_SetObjResult(interp, listObjPtr);
    return TCL_OK;
}


/*
 *---------------------------------------------------------------------------
 *
 * ColumnNonEmptyOp --
 *
 *      Returns a list of the rows with empty values in the given column.
 *
 * Results:
 *      A standard TCL result. If the tag or column index is invalid,
 *      TCL_ERROR is returned and an error message is left in the interpreter
 *      result.
 *
 *      tableName column nonempty colName
 *      
 *---------------------------------------------------------------------------
 */
static int
ColumnNonEmptyOp(ClientData clientData, Tcl_Interp *interp, int objc, 
                 Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    BLT_TABLE_COLUMN col;
    BLT_TABLE_ROW row;
    Tcl_Obj *listObjPtr;

    col = blt_table_get_column(interp, cmdPtr->table, objv[3]);
    if (col == NULL) {
        return TCL_ERROR;
    }
    listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL);
    for (row = blt_table_first_row(cmdPtr->table); row != NULL;
         row = blt_table_next_row(row)) {
        if (blt_table_value_exists(cmdPtr->table, row, col))  {
            Tcl_Obj *objPtr;
            
            objPtr = GetRowIndexObj(cmdPtr->table, row);
            Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
        }
    }
    Tcl_SetObjResult(interp, listObjPtr);
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * ColumnReorderOp --
 *
 *      pathName column reorder colNameList
 *
 *---------------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
ColumnReorderOp(ClientData clientData, Tcl_Interp *interp, int objc, 
                Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    BLT_TABLE_COLUMN *map;
    int i, elc;
    Tcl_Obj **elv;

    if (Tcl_ListObjGetElements(interp, objv[3], &elc, &elv) != TCL_OK) {
        return TCL_ERROR;
    }
    if (elc != blt_table_num_rows(cmdPtr->table)) {
        Tcl_AppendResult(interp,
            "# of elements in the column list does not match the # of columns",
            (char *)NULL);
        return TCL_ERROR;
    }
    for (i = 0; i < elc; i++) {
        BLT_TABLE_COLUMN col;
        
        col = blt_table_get_column(interp, cmdPtr->table, elv[i]);
        if (col == NULL) {
            return TCL_ERROR;
        }
    }
    map = Blt_AssertCalloc(elc, sizeof(BLT_TABLE_COLUMN));
    for (i = 0; i < elc; i++) {
        map[i] = blt_table_get_column(interp, cmdPtr->table, elv[i]);
    }
    blt_table_set_column_map(cmdPtr->table, map);
    return TCL_OK;
}


/*
 *---------------------------------------------------------------------------
 *
 * ColumnSetOp --
 *
 *      Sets one of values in a column.  One or more columns may be set
 *      using a tag.  The row order is always the table's current view of
 *      the table.  There may be less values than needed.
 * 
 * Results:
 *      A standard TCL result. If the tag or column index is invalid,
 *      TCL_ERROR is returned and an error message is left in the
 *      interpreter result.
 *      
 *      tableName column set colName ?rowName value ...?
 *
 *---------------------------------------------------------------------------
 */
static int
ColumnSetOp(ClientData clientData, Tcl_Interp *interp, int objc,
            Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    BLT_TABLE_ITERATOR ci;
    BLT_TABLE_COLUMN col;
    BLT_TABLE table;

    table = cmdPtr->table;
    /* May set more than one row with the same values. */
    if (IterateColumnsWithCreate(interp, table, objv[3], &ci) != TCL_OK) {
        return TCL_ERROR;
    }
    if (objc == 4) {
        return TCL_OK;
    }
    if ((objc - 4) & 1) {
        Tcl_AppendResult(interp, "odd # of row/value pairs: should be \"", 
                Tcl_GetString(objv[0]), " column assign col row value...", 
                (char *)NULL);
        return TCL_ERROR;
    }
    for (col = blt_table_first_tagged_column(&ci); col != NULL; 
         col = blt_table_next_tagged_column(&ci)) {
        int i;

        /* The remaining arguments are index/value pairs. */
        for (i = 4; i < objc; i += 2) {
            BLT_TABLE_ROW row;

            row = blt_table_get_row(interp, table, objv[i]);
            if (row == NULL) {
                /* Can't find the row. Create it and try to find it again. */
                if (MakeRows(interp, table, objv[i]) != TCL_OK) {
                    return TCL_ERROR;
                }
                row = blt_table_get_row(interp, table, objv[i]);
            }
            if (blt_table_set_obj(interp, table, row, col, objv[i + 1])
                != TCL_OK) {
                return TCL_ERROR;
            }
        }
    }
    return TCL_OK;
}


/*
 *---------------------------------------------------------------------------
 *
 * ColumnTagAddOp --
 *
 *      Adds a given tag to one or more columns.  Tag names can't start
 *      with a digit (to distinquish them from node ids) and can't be a
 *      reserved tag ("all", "add", or "end").
 *
 *      tableName column tag add tagName ?colName ...?
 *
 *---------------------------------------------------------------------------
 */
static int
ColumnTagAddOp(ClientData clientData, Tcl_Interp *interp, int objc,
               Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    BLT_TABLE table;
    int i;
    const char *tag;

    table = cmdPtr->table;
    tag = Tcl_GetString(objv[4]);
    if (blt_table_set_column_tag(interp, table, NULL, tag) != TCL_OK) {
        return TCL_ERROR;
    }
    for (i = 5; i < objc; i++) {
        BLT_TABLE_COLUMN col;
        BLT_TABLE_ITERATOR ci;

        if (blt_table_iterate_columns(interp, table, objv[i], &ci) != TCL_OK) {
            return TCL_ERROR;
        }
        for (col = blt_table_first_tagged_column(&ci); col != NULL; 
             col = blt_table_next_tagged_column(&ci)) {
            if (blt_table_set_column_tag(interp, table, col, tag) != TCL_OK) {
                return TCL_ERROR;
            }
        }    
    }
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * ColumnTagDeleteOp --
 *
 *      Removes a given tag from one or more columns. If a tag doesn't
 *      exist or is a reserved tag ("all" or "end"), nothing will be done
 *      and no error message will be returned.
 *
 *      tableName column tag delete tagName ?column...?
 *
 *---------------------------------------------------------------------------
 */
static int
ColumnTagDeleteOp(ClientData clientData, Tcl_Interp *interp, int objc, 
                  Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    BLT_TABLE table;
    BLT_TABLE_ITERATOR ci;
    BLT_TABLE_COLUMN col;
    const char *tag;
    
    table = cmdPtr->table;
    tag = Tcl_GetString(objv[4]);
    if (blt_table_iterate_columns_objv(interp, table, objc - 5, objv + 5, &ci)
        != TCL_OK) {
        return TCL_ERROR;
    }
    for (col = blt_table_first_tagged_column(&ci); col != NULL; 
         col = blt_table_next_tagged_column(&ci)) {
        if (blt_table_unset_column_tag(table, col, tag) != TCL_OK) {
            return TCL_ERROR;
        }
    }
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * ColumnTagExistsOp --
 *
 *      Returns the existence of a tag in the table.  If a column is
 *      specified then the tag is search for for that column.
 *
 *      tableName tag column exists tagName ?colName ...?
 *
 *---------------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
ColumnTagExistsOp(ClientData clientData, Tcl_Interp *interp, int objc, 
                  Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    int bool;
    const char *tag;
    BLT_TABLE table;

    tag = Tcl_GetString(objv[4]);
    table = cmdPtr->table;
    bool = (blt_table_get_tagged_columns(table, tag) != NULL);
    if ((bool) && (objc == 6)) {
        BLT_TABLE_COLUMN col;

        col = blt_table_get_column(interp, table, objv[5]);
        if (col == NULL) {
            bool = FALSE;
        } else {
            bool = blt_table_column_has_tag(table, col, tag);
        }
    } 
    Tcl_SetBooleanObj(Tcl_GetObjResult(interp), bool);
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * ColumnTagForgetOp --
 *
 *      Removes the given tags from all nodes.
 *
 *      tableName column tag forget ?tagName ...?
 *
 *---------------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
ColumnTagForgetOp(ClientData clientData, Tcl_Interp *interp, int objc, 
                  Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    int i;

    for (i = 4; i < objc; i++) {
        if (blt_table_forget_column_tag(cmdPtr->table, Tcl_GetString(objv[i]))
            != TCL_OK) {
            return TCL_ERROR;
        }
    }
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * ColumnTagGetOp --
 *
 *      Returns the tag names for a given column.  If one of more pattern
 *      arguments are provided, then only those matching tags are returned.
 *
 *      tableName column tag get colName ?pattern ...?
 *
 *---------------------------------------------------------------------------
 */
static int
ColumnTagGetOp(ClientData clientData, Tcl_Interp *interp, int objc,
               Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    Blt_HashEntry *hPtr;
    Blt_HashSearch hsearch;
    Blt_HashTable tagTable;
    BLT_TABLE table;
    BLT_TABLE_COLUMN col;
    BLT_TABLE_ITERATOR ci;
    Tcl_Obj *listObjPtr;
    int isNew;
    size_t lastIndex;
    
    table = cmdPtr->table;
    if (blt_table_iterate_columns(interp, table, objv[4], &ci) != TCL_OK) {
        return TCL_ERROR;
    }
    listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL);

    Blt_InitHashTable(&tagTable, BLT_STRING_KEYS);
    Blt_CreateHashEntry(&tagTable, "all", &isNew);
    
    /* Collect all the tags into a hash table. */
    lastIndex = blt_table_num_columns(table) - 1;
    for (col = blt_table_first_tagged_column(&ci); col != NULL; 
         col = blt_table_next_tagged_column(&ci)) {
        Blt_Chain chain;
        Blt_ChainLink link;
        int isNew;
        
        if (blt_table_column_index(table, col) == lastIndex) {
            Blt_CreateHashEntry(&tagTable, "end", &isNew);
        }
        chain = blt_table_get_column_tags(table, col);
        for (link = Blt_Chain_FirstLink(chain); link != NULL;
             link = Blt_Chain_NextLink(link)) {
            const char *tag;

            tag = Blt_Chain_GetValue(link);
            Blt_CreateHashEntry(&tagTable, tag, &isNew);
        }
        Blt_Chain_Destroy(chain);
    }
    for (hPtr = Blt_FirstHashEntry(&tagTable, &hsearch); hPtr != NULL;
         hPtr = Blt_NextHashEntry(&hsearch)) {
        int match;
        const char *tag;

        tag = Blt_GetHashKey(&tagTable, hPtr);
        match = TRUE;
        if (objc > 5) {
            int i;

            match = FALSE;
            for (i = 5; i < objc; i++) {
                if (Tcl_StringMatch(tag, Tcl_GetString(objv[i]))) {
                    match = TRUE;
                }
            }
        }
        if (match) {
            Tcl_Obj *objPtr;

            objPtr = Tcl_NewStringObj(tag, -1);
            Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
        }
    }
    Blt_DeleteHashTable(&tagTable);
    Tcl_SetObjResult(interp, listObjPtr);
    return TCL_OK;
}


static unsigned char *
GetColumnTagMatches(Tcl_Interp *interp, BLT_TABLE table, int objc, 
                    Tcl_Obj *const *objv)
{
    size_t numCols;
    int i;
    unsigned char *matches;

    numCols = blt_table_num_columns(table);
    matches = Blt_AssertCalloc(numCols, sizeof(unsigned char));
    /* Handle the reserved tags "all" or "end". */
    for (i = 0; i < objc; i++) {
        const char *tag;

        tag = Tcl_GetString(objv[i]);
        if (strcmp("all", tag) == 0) {
            size_t j;

            for (j = 0; j < numCols; j++) {
                matches[j] = TRUE;
            }
            return matches;             /* Don't care about other tags. */
        } 
        if (strcmp("end", tag) == 0) {
            matches[numCols - 1] = TRUE;
        }
    }
    /* Now check user-defined tags. */
    for (i = 0; i < objc; i++) {
        Blt_Chain chain;
        Blt_ChainLink link;
        const char *tag;
        
        tag = Tcl_GetString(objv[i]);
        if ((strcmp("all", tag) == 0) || (strcmp("end", tag) == 0)) {
            continue;
        }
        chain = blt_table_get_tagged_columns(table, tag);
        if (chain == NULL) {
            Blt_Free(matches);
            return NULL;
        }
        for (link = Blt_Chain_FirstLink(chain); link != NULL; 
             link = Blt_Chain_NextLink(link)) {
            BLT_TABLE_COLUMN col;
            size_t j;

            col = Blt_Chain_GetValue(link);
            j = blt_table_column_index(table, col);
            matches[j] = TRUE;
        }
    }
    return matches;
}


/*
 *---------------------------------------------------------------------------
 *
 * ColumnTagIndicesOp --
 *
 *      Returns column indices names for the given tags.  If one of more
 *      tag names are provided, then only those matching indices are
 *      returned.
 *
 *      tableName column tag indices ?tagName ...?
 *
 *---------------------------------------------------------------------------
 */
static int
ColumnTagIndicesOp(ClientData clientData, Tcl_Interp *interp, int objc, 
                   Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    Tcl_Obj *listObjPtr;
    unsigned char *matches;

    matches = GetColumnTagMatches(interp, cmdPtr->table, objc - 4, objv + 4);
    listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL);
    if (matches != NULL) {
        size_t i;

        for (i = 0; i < blt_table_num_columns(cmdPtr->table); i++) {
            if (matches[i]) {
                Tcl_ListObjAppendElement(interp, listObjPtr,
                                         Tcl_NewWideIntObj(i));
            }
        }
        Blt_Free(matches);
    }
    Tcl_SetObjResult(interp, listObjPtr);
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * ColumnTagLabelsOp --
 *
 *      Returns column labels for the given tags.  If one of more tag names
 *      are provided, then only those matching indices are returned.
 *
 *      tableName column tag labels ?tagName ...?
 *
 *---------------------------------------------------------------------------
 */
static int
ColumnTagLabelsOp(ClientData clientData, Tcl_Interp *interp, int objc, 
                  Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    Tcl_Obj *listObjPtr;
    unsigned char *matches;

    matches = GetColumnTagMatches(interp, cmdPtr->table, objc - 4, objv + 4);
    listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL);
    if (matches != NULL) {
        size_t j;

        for (j = 0; j < blt_table_num_columns(cmdPtr->table); j++) {
            if (matches[j]) {
                BLT_TABLE_COLUMN col;
                Tcl_Obj *objPtr;
                
                col = blt_table_column(cmdPtr->table, j);
                objPtr = Tcl_NewStringObj(blt_table_column_label(col), -1);
                Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
            }
        }
        Blt_Free(matches);
    }
    Tcl_SetObjResult(interp, listObjPtr);
    return TCL_OK;
}


/*
 *---------------------------------------------------------------------------
 *
 * ColumnTagRangeOp --
 *
 *      Adds one or more tags for a given column.  Tag names can't start
 *      with a digit (to distinquish them from node ids) and can't be a
 *      reserved tag ("all" or "end").
 *
 *      tableName column tag range fromColumn toColumn ?tagName ...?
 *
 *---------------------------------------------------------------------------
 */
static int
ColumnTagRangeOp(ClientData clientData, Tcl_Interp *interp, int objc, 
                 Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    BLT_TABLE table;
    BLT_TABLE_COLUMN from, to;
    int i;

    table = cmdPtr->table;
    from = blt_table_get_column(interp, table, objv[4]);
    if (from == NULL) {
        return TCL_ERROR;
    }
    to = blt_table_get_column(interp, table, objv[5]);
    if (to == NULL) {
        return TCL_ERROR;
    }
    if (blt_table_column_index(table, from) > blt_table_column_index(table, to)) {
        return TCL_OK;
    }
    for (i = 6; i < objc; i++) {
        const char *tag;
        size_t j;
        
        tag = Tcl_GetString(objv[i]);
        for (j = blt_table_column_index(table, from);
             j <= blt_table_column_index(table, to); j++) {
            BLT_TABLE_COLUMN col;

            col = blt_table_column(table, j);
            if (blt_table_set_column_tag(interp, table, col, tag) != TCL_OK) {
                return TCL_ERROR;
            }
        }    
    }
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * ColumnTagNamesOp --
 *
 *      Returns a list of all the column tags.  One of more tag pattern
 *      arguments can be given.
 *
 *      tableName column tag names ?pattern ...?
 *
 *---------------------------------------------------------------------------
 */
static int
ColumnTagNamesOp(ClientData clientData, Tcl_Interp *interp, int objc, 
                  Tcl_Obj *const *objv)
{
    Blt_HashTable *tablePtr;
    Blt_HashEntry *hPtr;
    Blt_HashSearch iter;
    Cmd *cmdPtr = clientData;
    Tcl_Obj *listObjPtr;
    int allMatch, endMatch;
    int i;
    
    tablePtr = blt_table_get_column_tag_table(cmdPtr->table);
    listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL);
    for (hPtr = Blt_FirstHashEntry(tablePtr, &iter); hPtr != NULL;
         hPtr = Blt_NextHashEntry(&iter)) {
        const char *tag;
        int i;
        int match;
        
        tag = Blt_GetHashKey(tablePtr, hPtr);
        match = (objc == 4);
        for (i = 4; i < objc; i++) {
            const char *pattern;

            pattern = Tcl_GetString(objv[i]);
            if (Tcl_StringMatch(tag, pattern)) {
                match = TRUE;
                break;                  /* Found match. */
            }
        }
        if (match) {
            Tcl_Obj *objPtr;

            objPtr = Tcl_NewStringObj(tag, -1);
            Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
        }
    }
    allMatch = endMatch = (objc == 4);
    for (i = 4; i < objc; i++) {
        const char *pattern;

        pattern = Tcl_GetString(objv[i]);
        if (Tcl_StringMatch("all", pattern)) {
           allMatch = TRUE;
        }
        if (Tcl_StringMatch("end", pattern)) {
           endMatch = TRUE;
        }
    }
    if (allMatch) {
        Tcl_Obj *objPtr;

        objPtr = Tcl_NewStringObj("all", 3);
        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
    }
    if (endMatch) {
        Tcl_Obj *objPtr;

        objPtr = Tcl_NewStringObj("end", 3);
        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
    }
    Tcl_SetObjResult(interp, listObjPtr);
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * ColumnTagSetOp --
 *
 *      Adds one or more tags for a given column.  Tag names can't start
 *      with a digit (to distinquish them from node ids) and can't be a
 *      reserved tag ("all" or "end").
 *
 *      tableName column tag set colName ?tagName ...?
 *
 *---------------------------------------------------------------------------
 */
static int
ColumnTagSetOp(ClientData clientData, Tcl_Interp *interp, int objc,
               Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    BLT_TABLE table;
    BLT_TABLE_ITERATOR ci;
    int i;

    table = cmdPtr->table;
    if (blt_table_iterate_columns(interp, table, objv[4], &ci) != TCL_OK) {
        return TCL_ERROR;
    }
    for (i = 5; i < objc; i++) {
        const char *tag;
        BLT_TABLE_COLUMN col;

        tag = Tcl_GetString(objv[i]);
        for (col = blt_table_first_tagged_column(&ci); col != NULL; 
             col = blt_table_next_tagged_column(&ci)) {
            if (blt_table_set_column_tag(interp, table, col, tag) != TCL_OK) {
                return TCL_ERROR;
            }
        }    
    }
    return TCL_OK;
}


/*
 *---------------------------------------------------------------------------
 *
 * ColumnTagUnsetOp --
 *
 *      Removes one or more tags from a given column. If a tag doesn't
 *      exist or is a reserved tag ("all" or "end"), nothing will be done
 *      and no error message will be returned.
 *
 *      tableName column tag unset colName ?tagName ...?
 *
 *---------------------------------------------------------------------------
 */
static int
ColumnTagUnsetOp(ClientData clientData, Tcl_Interp *interp, int objc, 
                 Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    BLT_TABLE table;
    BLT_TABLE_ITERATOR ci;
    int i;

    table = cmdPtr->table;
    if (blt_table_iterate_columns(interp, table, objv[4], &ci) != TCL_OK) {
        return TCL_ERROR;
    }
    for (i = 5; i < objc; i++) {
        const char *tag;
        BLT_TABLE_COLUMN col;
        
        tag = Tcl_GetString(objv[i]);
        for (col = blt_table_first_tagged_column(&ci); col != NULL; 
             col = blt_table_next_tagged_column(&ci)) {
            if (blt_table_unset_column_tag(table, col, tag) != TCL_OK) {
                return TCL_ERROR;
            }
        }
    }    
    return TCL_OK;
}


/*
 *---------------------------------------------------------------------------
 *
 * ColumnTagOp --
 *
 *      This procedure is invoked to process tag operations.
 *
 * Results:
 *      A standard TCL result.
 *
 * Side Effects:
 *      See the user documentation.
 *
 *---------------------------------------------------------------------------
 */
static Blt_OpSpec columnTagOps[] =
{
    {"add",     1, ColumnTagAddOp,     5, 0, "tagName ?colName ...?",},
    {"delete",  1, ColumnTagDeleteOp,  5, 0, "tagName ?colName ...?",},
    {"exists",  1, ColumnTagExistsOp,  4, 5, "tagName ?colName?",},
    {"forget",  1, ColumnTagForgetOp,  4, 0, "?tagName ...?",},
    {"get",     1, ColumnTagGetOp,     5, 0, "colName ?pattern ...?",},
    {"indices", 1, ColumnTagIndicesOp, 4, 0, "?tagName ...?",},
    {"labels",  1, ColumnTagLabelsOp,  4, 0, "?tagName ...?",},
    {"names",   1, ColumnTagNamesOp,   4, 0, "?pattern ...?",},
    {"range",   1, ColumnTagRangeOp,   6, 0, "from to ?tagName ...?",},
    {"set",     1, ColumnTagSetOp,     5, 0, "colName ?tagName ...?",},
    {"unset",   1, ColumnTagUnsetOp,   5, 0, "colName ?tagName ...?",},

};

static int numColumnTagOps = sizeof(columnTagOps) / sizeof(Blt_OpSpec);

static int
ColumnTagOp(ClientData clientData, Tcl_Interp *interp, int objc,
            Tcl_Obj *const *objv)
{
    Tcl_ObjCmdProc *proc;

    int result;

    proc = Blt_GetOpFromObj(interp, numColumnTagOps, columnTagOps, BLT_OP_ARG3,
        objc, objv, 0);
    if (proc == NULL) {
        return TCL_ERROR;
    }
    result = (*proc)(clientData, interp, objc, objv);
    return result;
}

/*
 *---------------------------------------------------------------------------
 *
 * ColumnTypeOp --
 *
 *      Reports and/or sets the type of a column.  
 * 
 * Results:
 *      A standard TCL result.  If successful, the old column label is
 *      returned in the interpreter result.  If the column index is invalid,
 *      TCL_ERROR is returned and an error message is left in the interpreter
 *      result.
 *      
 *      tableName column type colName
 *      tableName column type colName ?typeName colName typeName ...?
 *
 *---------------------------------------------------------------------------
 */
static int
ColumnTypeOp(ClientData clientData, Tcl_Interp *interp, int objc,
             Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    BLT_TABLE table;
    int i;
    
    table = cmdPtr->table;
    if (objc == 4) {
        BLT_TABLE_ITERATOR ci;
        BLT_TABLE_COLUMN col;
        Tcl_Obj *listObjPtr;

        listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL);
        if (blt_table_iterate_columns(interp, table, objv[3], &ci) != TCL_OK) {
            return TCL_ERROR;
        }
        for (col = blt_table_first_tagged_column(&ci); col != NULL; 
             col = blt_table_next_tagged_column(&ci)) {
            Tcl_Obj *objPtr;
            BLT_TABLE_COLUMN_TYPE type;
            
            type = blt_table_column_type(col);
            objPtr = Tcl_NewStringObj(blt_table_column_type_to_name(type), -1);
            Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
        }
        Tcl_SetObjResult(interp, listObjPtr);
        return TCL_OK;
    }
    objc -= 3;
    objv += 3;
    if (objc & 0x1) {
        Tcl_AppendResult(interp, "odd # of arguments: should ?index type ...?", 
                (char *)NULL);
        return TCL_ERROR;
    }
    for (i = 0; i < objc; i += 2) {
        BLT_TABLE_ITERATOR ci;
        BLT_TABLE_COLUMN col;
        BLT_TABLE_COLUMN_TYPE newType;

        if (blt_table_iterate_columns(interp, table, objv[i], &ci) != TCL_OK) {
            return TCL_ERROR;
        }
        newType = blt_table_name_to_column_type(Tcl_GetString(objv[i+1]));
        if (newType == TABLE_COLUMN_TYPE_UNKNOWN) {
            Tcl_AppendResult(interp, "unknown column type \"", 
                             Tcl_GetString(objv[i+1]), "\"", (char *)NULL);
            return TCL_ERROR;
        }
        for (col = blt_table_first_tagged_column(&ci); col != NULL; 
             col = blt_table_next_tagged_column(&ci)) {
            BLT_TABLE_COLUMN_TYPE type;

            type = blt_table_column_type(col);
            if (newType != type) {
                if (blt_table_set_column_type(interp, table, col, newType)
                    != TCL_OK) {
                    return TCL_ERROR;
                }
            }
        }
    }
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * ColumnUnsetOp --
 *
 *      Unsets one or more columns of values.  One or more columns may be
 *      unset (using tags or multiple arguments). It's an error if the
 *      column doesn't exist.
 * 
 * Results:
 *      A standard TCL result. If the tag or column index is invalid,
 *      TCL_ERROR is returned and an error message is left in the
 *      interpreter result.
 *      
 *      tableName column unset colName ?indices ...?
 *
 *---------------------------------------------------------------------------
 */
static int
ColumnUnsetOp(ClientData clientData, Tcl_Interp *interp, int objc,
              Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    BLT_TABLE table;
    BLT_TABLE_ITERATOR ri, ci;
    BLT_TABLE_COLUMN col;
    int result;

    table = cmdPtr->table;
    if (blt_table_iterate_columns(interp, table, objv[3], &ci) != TCL_OK) {
        return TCL_ERROR;
    }
    if (blt_table_iterate_rows_objv(interp, table, objc - 4, objv + 4 , &ri) 
        != TCL_OK) {
        return TCL_ERROR;
    }
    result = TCL_ERROR;
    for (col = blt_table_first_tagged_column(&ci); col != NULL; 
         col = blt_table_next_tagged_column(&ci)) {
        BLT_TABLE_ROW row;

        for (row = blt_table_first_tagged_row(&ri); row != NULL;
             row = blt_table_next_tagged_row(&ri)) {
            if (blt_table_unset_value(table, row, col) != TCL_OK) {
                goto error;
            }
        }
    }
    result = TCL_OK;
 error:
    blt_table_free_iterator_objv(&ri);
    return result;
}


/*
 *---------------------------------------------------------------------------
 *
 * ColumnValuesOp --
 *
 *      Retrieves a column of values.  The column argument can be either a
 *      tag, label, or column index.  If it is a tag, it must refer to
 *      exactly one column.
 * 
 * Results:
 *      A standard TCL result.  If successful, a list of values is returned
 *      in the interpreter result.  If the column index is invalid,
 *      TCL_ERROR is returned and an error message is left in the
 *      interpreter result.
 *      
 *      tableName column values colName ?valueList?
 *
 *---------------------------------------------------------------------------
 */
static int
ColumnValuesOp(ClientData clientData, Tcl_Interp *interp, int objc,
               Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    BLT_TABLE table;
    BLT_TABLE_COLUMN col;

    table = cmdPtr->table;
    col = blt_table_get_column(interp, cmdPtr->table, objv[3]);
    if (col == NULL) {
        return TCL_ERROR;
    }
    if (objc == 4) {
        BLT_TABLE_ROW row;
        Tcl_Obj *listObjPtr;

        listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL);
        for (row = blt_table_first_row(cmdPtr->table); row != NULL;
             row = blt_table_next_row(row)) {
            Tcl_Obj *objPtr;
            
            objPtr = blt_table_get_obj(cmdPtr->table, row, col);
            if (objPtr == NULL) {
                objPtr = Tcl_NewStringObj(cmdPtr->emptyString, -1);
            }
            Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
        }
        Tcl_SetObjResult(interp, listObjPtr);
    } else {
        Tcl_Obj **elv;
        int elc;
        size_t i;
        BLT_TABLE_ROW row;

        if (Tcl_ListObjGetElements(interp, objv[4], &elc, &elv) != TCL_OK) {
            return TCL_ERROR;
        }
        if (elc > blt_table_num_rows(table)) {
            size_t needed;

            needed = elc - blt_table_num_rows(table);
            if (blt_table_extend_rows(interp, table, needed, NULL)
                != TCL_OK) {
                return TCL_ERROR;
            }
        }
        for (i = 0, row = blt_table_first_row(cmdPtr->table); i < elc; 
             i++, row = blt_table_next_row(row)) {

            if (blt_table_set_obj(interp, table, row, col, elv[i]) != TCL_OK) {
                return TCL_ERROR;
            }
        }
    }
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * ColumnOp --
 *
 *      Parses the given command line and calls one of several
 *      column-specific operations.
 *      
 * Results:
 *      Returns a standard TCL result.  It is the result of operation
 *      called.
 *
 *---------------------------------------------------------------------------
 */
static Blt_OpSpec columnOps[] =
{
    {"copy",      2, ColumnCopyOp,    4, 0, "destColumn srcColumn ?switches?",},
    {"create",    2, ColumnCreateOp,  3, 0, "?switches?",},
    {"delete",    2, ColumnDeleteOp,  3, 0, "?colName ...?",},
    {"duplicate", 2, ColumnDupOp,     3, 0, "?colName ...?",},
    {"empty",     2, ColumnEmptyOp,   4, 4, "colName",},
    {"exists",    3, ColumnExistsOp,  4, 4, "colName",},
    {"extend",    3, ColumnExtendOp,  4, 0, "numColumns ?switches?",},
    {"get",       1, ColumnGetOp,     4, 0, "colName ?switches?",},
    {"index",     4, ColumnIndexOp,   4, 4, "colName",},
    {"indices",   4, ColumnIndicesOp, 3, 0, "?pattern ...?",},
    {"join",      1, ColumnJoinOp,    4, 0, "tableName ?switches?",},
    {"label",     5, ColumnLabelOp,   4, 0, "colName ?label?",},
    {"labels",    6, ColumnLabelsOp,  3, 4, "?labelList?",},
    {"move",      1, ColumnMoveOp,    6, 0, "destColumn firstColumn lastColumn ?switches?"},
    {"names",     2, ColumnNamesOp,   3, 0, "?pattern ...?",},
    {"nonempty",  2, ColumnNonEmptyOp,4, 4, "colName",},
    {"reorder",   1, ColumnReorderOp, 4, 4, "colName",},
    {"set",       1, ColumnSetOp,     5, 0, "colName rowName ?value ...?",},
    {"tag",       2, ColumnTagOp,     3, 0, "op args...",},
    {"type",      2, ColumnTypeOp,    4, 0, "colName ?typeName colName typeName ...?",},
    {"unset",     1, ColumnUnsetOp,   4, 0, "colName ?indices ...?",},
    {"values",    1, ColumnValuesOp,  4, 5, "colName ?valueList?",},
};

static int numColumnOps = sizeof(columnOps) / sizeof(Blt_OpSpec);

static int
ColumnOp(ClientData clientData, Tcl_Interp *interp, int objc,
         Tcl_Obj *const *objv)
{
    Tcl_ObjCmdProc *proc;
    int result;

    proc = Blt_GetOpFromObj(interp, numColumnOps, columnOps, BLT_OP_ARG2, objc, 
        objv, 0);
    if (proc == NULL) {
        return TCL_ERROR;
    }
    result = (*proc)(clientData, interp, objc, objv);
    return result;
}

/*
 *---------------------------------------------------------------------------
 *
 * CopyOp --
 *
 *      Copies the rows and columns from the source table given.  Any data
 *      in the table is first deleted.
 *
 *      tableName copy srcTableName
 *
 *---------------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
CopyOp(ClientData clientData, Tcl_Interp *interp, int objc,
       Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    BLT_TABLE srcTable;
    int result;

    if (blt_table_open(interp, Tcl_GetString(objv[2]), &srcTable) != TCL_OK) {
        return TCL_ERROR;
    }
    result = CopyTable(interp, srcTable, cmdPtr->table);
    blt_table_close(srcTable);
    return result;
}

/*
 *---------------------------------------------------------------------------
 *
 * DirOp --
 *
 *      tableName dir dirPath ?switches ...?
 *
 *---------------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
DirOp(ClientData clientData, Tcl_Interp *interp, int objc,
      Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    ReadDirectory reader;
    Tcl_Obj **patterns, **entries, *listObjPtr;
    Tcl_StatBuf stat;
    int i, numPatterns, numEntries;
    unsigned int patternFlags;
    Tcl_GlobTypeData data = {
        0, TCL_GLOB_PERM_R, /* macType*/NULL, /*macCreator*/NULL
    };

    memset(&reader, 0, sizeof(reader));
    reader.mask = READ_DIR_DEFAULT;
    if (Blt_ParseSwitches(interp, dirSwitches, objc - 3, objv + 3, &reader,
        BLT_SWITCH_DEFAULTS) < 0) {
        return TCL_ERROR;
    }
    data.type = reader.type;
    data.perm = reader.perm;
    numPatterns = 0;
    if (reader.patternsObjPtr != NULL) {
        if (Tcl_ListObjGetElements(interp, reader.patternsObjPtr, 
                &numPatterns, &patterns) != TCL_OK) {
            return TCL_ERROR;           /* Can't split patterns. */
        }
    }
    patternFlags = 0;
#if (_TCL_VERSION > _VERSION(8,5,0)) 
    if (reader.flags & READ_DIR_NOCASE) {
        patternFlags =  TCL_MATCH_NOCASE;
    }
#endif
    listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL);
    if (Tcl_FSStat(objv[2], &stat) != 0) {
        Tcl_AppendResult(interp, "Can't stat directory \"",
                         Tcl_GetString(objv[2]), "\": ",
                         Tcl_PosixError(interp), (char *)NULL);
        goto error;
    }
    if (!S_ISDIR(stat.st_mode)) {
        Tcl_AppendResult(interp, "\"", Tcl_GetString(objv[2]),
                         "\" is not a directory", (char *)NULL);
        goto error;
    }
    if (Tcl_FSMatchInDirectory(interp, listObjPtr, objv[2], "*", &data)
        != TCL_OK) {
        Tcl_AppendResult(interp, "Can't read directory \"",
                         Tcl_GetString(objv[2]), "\": ",
                         Tcl_PosixError(interp), (char *)NULL);
        goto error;
    }
    if (Tcl_ListObjGetElements(interp, listObjPtr, &numEntries, &entries)
        != TCL_OK) {
        goto error;
    }
    for (i = 0; i < numEntries; i++) {
        Tcl_Obj *partsObjPtr, *tailObjPtr;
        Tcl_StatBuf stat;
        const char *label;
        int isMatch, numParts;

        if (Tcl_FSConvertToPathType(interp, entries[i]) != TCL_OK) {
           goto error;                 /* Can't convert path. */
        }
        memset(&stat, 0, sizeof(Tcl_StatBuf));
        if (Tcl_FSStat(entries[i], &stat) < 0) {
            continue;                   /* Can't stat entry. */
        }
        /* Get the tail of the path. */
        partsObjPtr = Tcl_FSSplitPath(entries[i], &numParts);
        if ((partsObjPtr == NULL) || (numParts == 0)) {
            goto error;                 /* Can't split path. */
        }
        Tcl_IncrRefCount(partsObjPtr);
        Tcl_ListObjIndex(NULL, partsObjPtr, numParts - 1, &tailObjPtr);
        label = Tcl_GetString(tailObjPtr);

        if (label[0] == '.') {
            if (label[1] == '\0') {
                Tcl_DecrRefCount(partsObjPtr);
                continue;               /* Ignore . */
            }
            if ((label[1] == '.') && (label[2] == '\0')) {
                Tcl_DecrRefCount(partsObjPtr);
                continue;               /* Ignore .. */
            }
            /* Workaround bug in Tcl_FSSplitPath. Files that start with "~"
             * are prepended with "./" */
            if (label[1] == '/') {
                label += 2;
            }
        }
        isMatch = TRUE;
        if (numPatterns > 0) {          /* Match files or subdirectories
                                         * against patterns. */
            int j;
            
            isMatch = FALSE;
            for (j = 0; j < numPatterns; j++) {
                const char *pattern;
                
                pattern = Tcl_GetString(patterns[j]);
                if (Tcl_StringCaseMatch(label, pattern, patternFlags)) {
                    isMatch = TRUE;
                    break;              /* Found a match. */
                }
            }
        }
        if (isMatch) {
            ExportToTable(interp, cmdPtr->table, label, &stat, &reader);
        }
        Tcl_DecrRefCount(partsObjPtr);
    }
    Tcl_DecrRefCount(listObjPtr);
    return TCL_OK;
 error:
    Tcl_DecrRefCount(listObjPtr);
    return TCL_ERROR;
}

/*
 *---------------------------------------------------------------------------
 *
 * DumpOp --
 *
 *      tableName dump ?switches?
 *
 *---------------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
DumpOp(ClientData clientData, Tcl_Interp *interp, int objc,
       Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    BLT_TABLE table;
    DumpSwitches switches;
    int result;
    Tcl_DString ds;
    int closeChannel;
    Tcl_Channel channel;

    closeChannel = FALSE;
    channel = NULL;
    table = cmdPtr->table;
    result = TCL_ERROR;
    memset(&switches, 0, sizeof(switches));
    switches.channel = channel;
    switches.dsPtr = &ds;
    rowIterSwitch.clientData = cmdPtr->table;
    columnIterSwitch.clientData = cmdPtr->table;
    blt_table_iterate_all_rows(table, &switches.ri);
    blt_table_iterate_all_columns(table, &switches.ci);

    if (Blt_ParseSwitches(interp, dumpSwitches, objc - 2, objv + 2, &switches, 
        BLT_SWITCH_DEFAULTS) < 0) {
        goto error;
    }
    if (switches.fileObjPtr != NULL) {
        const char *fileName;

        fileName = Tcl_GetString(switches.fileObjPtr);

        closeChannel = TRUE;
        if ((fileName[0] == '@') && (fileName[1] != '\0')) {
            int mode;
            
            channel = Tcl_GetChannel(interp, fileName+1, &mode);
            if (channel == NULL) {
                goto error;
            }
            if ((mode & TCL_WRITABLE) == 0) {
                Tcl_AppendResult(interp, "can't dump table: channel \"", 
                        fileName, "\" not opened for writing", (char *)NULL);
                goto error;
            }
            closeChannel = FALSE;
        } else {
            channel = Tcl_OpenFileChannel(interp, fileName, "w", 0666);
            if (channel == NULL) {
                goto error;
            }
        }
        switches.channel = channel;
    }
    Tcl_DStringInit(&ds);
    result = DumpTable(interp, table, &switches);
    if ((switches.channel == NULL) && (result == TCL_OK)) {
        Tcl_DStringResult(interp, &ds);
    }
    Tcl_DStringFree(&ds);
 error:
    if (closeChannel) {
        Tcl_Close(interp, channel);
    }
    Blt_FreeSwitches(dumpSwitches, &switches, 0);
    return result;
}

/*
 *---------------------------------------------------------------------------
 *
 * DupOp --
 *
 *      Duplicates the rows and columns from the source table given into a
 *      new table.
 *
 *      tableName dup ?destTableName?
 *
 *---------------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
DuplicateOp(ClientData clientData, Tcl_Interp *interp, int objc,
            Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    if (objc == 3) {
        BLT_TABLE srcTable;
        int result;

        if (blt_table_open(interp, Tcl_GetString(objv[2]), &srcTable) 
            != TCL_OK) {
            return TCL_ERROR;
        }
        result = CopyTable(interp, srcTable, cmdPtr->table);
        blt_table_close(srcTable);
        return result;
    } else {
        Tcl_DString ds;
        const char *instName;
        BLT_TABLE destTable;

        Tcl_DStringInit(&ds);
        instName = GenerateName(interp, "", "", &ds);
        if (instName == NULL) {
            goto error;
        }
        if (blt_table_create(interp, instName, &destTable) == TCL_OK) {
            int result;

            NewTableCmd(interp, destTable, instName);
            result = CopyTable(interp, cmdPtr->table, destTable);
            if (result != TCL_ERROR) {
                Tcl_SetStringObj(Tcl_GetObjResult(interp), instName, -1);
            }
            Tcl_DStringFree(&ds);
            return result;
        }
    error:
        Tcl_DStringFree(&ds);
        return TCL_ERROR;
    }
    return TCL_ERROR;
}

/*
 *---------------------------------------------------------------------------
 *
 * EmptyValueOp --
 *
 *      tableName emptyvalue ?value?
 *
 *---------------------------------------------------------------------------
 */
static int
EmptyValueOp(ClientData clientData, Tcl_Interp *interp, int objc,
             Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    Tcl_SetStringObj(Tcl_GetObjResult(interp), cmdPtr->emptyString, -1);
    if (objc == 3) {
        if (cmdPtr->emptyString != NULL) {
            Blt_Free(cmdPtr->emptyString);
            cmdPtr->emptyString = Blt_AssertStrdup(Tcl_GetString(objv[2]));
        }
    }
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * ExistsOp --
 *
 *      tableName exists rowName colName
 *
 *---------------------------------------------------------------------------
 */
static int
ExistsOp(ClientData clientData, Tcl_Interp *interp, int objc,
         Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    int bool;
    BLT_TABLE_ROW row;
    BLT_TABLE_COLUMN col;

    bool = FALSE;
    row = blt_table_get_row(NULL, cmdPtr->table, objv[2]);
    col = blt_table_get_column(NULL, cmdPtr->table, objv[3]);
    if ((row != NULL) && (col != NULL)) {
        bool = blt_table_value_exists(cmdPtr->table, row, col);
    }
    Tcl_SetBooleanObj(Tcl_GetObjResult(interp), bool);
    return TCL_OK;
}
/*
 *---------------------------------------------------------------------------
 *
 * ExportOp --
 *
 *      Parses the given command line and calls one of several
 *      export-specific operations.
 *      
 * Results:
 *      Returns a standard TCL result.  It is the result of operation called.
 *
 *---------------------------------------------------------------------------
 */
static int
ExportOp(ClientData clientData, Tcl_Interp *interp, int objc,
         Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    Blt_HashEntry *hPtr;
    DataFormat *fmtPtr;
    TableCmdInterpData *dataPtr;
    const char *fmt;

    dataPtr = GetTableCmdInterpData(interp);
    if (objc == 2) {
        Blt_HashSearch iter;

        for (hPtr = Blt_FirstHashEntry(&dataPtr->fmtTable, &iter); 
             hPtr != NULL; hPtr = Blt_NextHashEntry(&iter)) {
            fmtPtr = Blt_GetHashValue(hPtr);
            if (fmtPtr->exportProc != NULL) {
                Tcl_AppendElement(interp, fmtPtr->name);
            }
        }
        return TCL_OK;
    }
    fmt = Tcl_GetString(objv[2]);
    hPtr = Blt_FindHashEntry(&dataPtr->fmtTable, fmt);
    if (hPtr == NULL) {
        LoadFormat(interp, fmt);
        hPtr = Blt_FindHashEntry(&dataPtr->fmtTable, fmt);
        if (hPtr == NULL) {
            Tcl_AppendResult(interp, "can't export \"", Tcl_GetString(objv[2]),
                         "\": format not registered", (char *)NULL);
            return TCL_ERROR;
        }
    }
    fmtPtr = Blt_GetHashValue(hPtr);
    if ((fmtPtr->flags & FMT_LOADED) == 0) {
        LoadFormat(interp, Tcl_GetString(objv[2]));
    }
    if (fmtPtr->exportProc == NULL) {
        Tcl_AppendResult(interp, "can't find table export procedure for \"", 
                         fmtPtr->name, "\" format", (char *)NULL);
        return TCL_ERROR;
    }
    return (*fmtPtr->exportProc) (cmdPtr->table, interp, objc, objv);
}

/*
 *---------------------------------------------------------------------------
 *
 * FindOp --
 *
 *      Parses the given command line and calls one of several
 *      export-specific operations.
 *      
 * Results:
 *      Returns a standard TCL result.  It is the result of operation called.
 *
 *      tableName find expr ?switches?
 *
 *---------------------------------------------------------------------------
 */
static int
FindOp(ClientData clientData, Tcl_Interp *interp, int objc,
       Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    FindSwitches switches;
    int result;

    memset(&switches, 0, sizeof(switches));
    rowIterSwitch.clientData = cmdPtr->table;
    blt_table_iterate_all_rows(cmdPtr->table, &switches.iter);
    if (Blt_ParseSwitches(interp, findSwitches, objc - 3, objv + 3, 
        &switches, BLT_SWITCH_DEFAULTS) < 0) {
        return TCL_ERROR;
    }
    switches.table = cmdPtr->table;
    Blt_InitHashTable(&switches.varTable, BLT_ONE_WORD_KEYS);
    result = FindRows(interp, cmdPtr->table, objv[2], &switches);
    Blt_FreeSwitches(findSwitches, &switches, 0);
    return result;
}

/*
 *---------------------------------------------------------------------------
 *
 * GetOp --
 *
 *      Retrieves the value from a given table for a designated row,column
 *      location.
 *
 *      Normally it's an error if the column or row key is invalid or the
 *      data slot is empty (the Tcl_Obj is NULL). But if an extra argument
 *      is provided, then it is returned as a default value.
 * 
 * Results:
 *      A standard TCL result. If the tag or index is invalid, TCL_ERROR is
 *      returned and an error message is left in the interpreter result.
 *      
 *      tableName get rowName colName ?defValue?
 *
 *---------------------------------------------------------------------------
 */
static int
GetOp(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    Tcl_Obj *objPtr;
    BLT_TABLE_ROW row;
    BLT_TABLE_COLUMN col;

    row = blt_table_get_row(interp, cmdPtr->table, objv[2]);
    if (row == NULL) {
        if (objc == 5) {
            objPtr = objv[4];
            goto done;
        }
        return TCL_ERROR;
    } 
    col = blt_table_get_column(interp, cmdPtr->table, objv[3]);
    if (col == NULL) {
        if (objc == 5) {
            objPtr = objv[4];
            goto done;
        }
        return TCL_ERROR;
    } 
    objPtr = blt_table_get_obj(cmdPtr->table, row, col);
    if (objPtr == NULL) {
        if (objc == 5) {
            objPtr = objv[4];
        } else {
            objPtr = Tcl_NewStringObj(cmdPtr->emptyString, -1);
        }
    }
 done:
    Tcl_SetObjResult(interp, objPtr);
    return TCL_OK;
}


/*
 *---------------------------------------------------------------------------
 *
 * ImportOp --
 *
 *      Parses the given command line and calls one of several import-specific
 *      operations.
 *      
 * Results:
 *      Returns a standard TCL result.  It is the result of operation called.
 *
 *---------------------------------------------------------------------------
 */
static int
ImportOp(ClientData clientData, Tcl_Interp *interp, int objc,
         Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    Blt_HashEntry *hPtr;
    DataFormat *fmtPtr;
    TableCmdInterpData *dataPtr;
    const char *fmt;

    dataPtr = GetTableCmdInterpData(interp);
    if (objc == 2) {
        Blt_HashSearch iter;

        for (hPtr = Blt_FirstHashEntry(&dataPtr->fmtTable, &iter); 
             hPtr != NULL; hPtr = Blt_NextHashEntry(&iter)) {
            fmtPtr = Blt_GetHashValue(hPtr);
            if (fmtPtr->importProc != NULL) {
                Tcl_AppendElement(interp, fmtPtr->name);
            }
        }
        return TCL_OK;
    }
    fmt = Tcl_GetString(objv[2]);
    hPtr = Blt_FindHashEntry(&dataPtr->fmtTable, fmt);
    if (hPtr == NULL) {
        LoadFormat(interp, fmt);
        hPtr = Blt_FindHashEntry(&dataPtr->fmtTable, fmt);
        if (hPtr == NULL) {
            Tcl_AppendResult(interp, "can't import table format \"", fmt,
                             "\": format not registered", (char *)NULL);
            return TCL_ERROR;
        }
    }
    fmtPtr = Blt_GetHashValue(hPtr);
    if ((fmtPtr->flags & FMT_LOADED) == 0) {
        LoadFormat(interp, Tcl_GetString(objv[2]));
    }
    if (fmtPtr->importProc == NULL) {
        Tcl_AppendResult(interp, "can't find table import procedure for \"", 
                fmtPtr->name, "\" format", (char *)NULL);
        return TCL_ERROR;
    }
    return (*fmtPtr->importProc) (cmdPtr->table, interp, objc, objv);
}

/*
 *---------------------------------------------------------------------------
 *
 * KeysOp --
 *
 *      This procedure is invoked to process key operations.
 *
 * Results:
 *      A standard TCL result.
 *
 * Side Effects:
 *      See the user documentation.
 *
 *      tableName keys ?key ...?
 *
 *---------------------------------------------------------------------------
 */
static int
KeysOp(ClientData clientData, Tcl_Interp *interp, int objc,
       Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    BLT_TABLE_COLUMN *keys;
    int numKeys;
    BLT_TABLE table;
    int i;

    if (objc == 2) {
        Tcl_Obj *listObjPtr;

        numKeys = blt_table_get_keys(cmdPtr->table, &keys);
        listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL);
        for (i = 0; i < numKeys; i++) {
            BLT_TABLE_COLUMN col;
            Tcl_Obj *objPtr;
            
            col = keys[i];
            objPtr = Tcl_NewStringObj(blt_table_column_label(col), -1);
            Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
        }
        Tcl_SetObjResult(interp, listObjPtr);
        return TCL_OK;
    }
    table = cmdPtr->table;
    keys = Blt_AssertMalloc(sizeof(BLT_TABLE_COLUMN) * (objc - 2));
    for (numKeys = 0, i = 2; i < objc; i++, numKeys++) {
        BLT_TABLE_COLUMN col;

        col = blt_table_get_column(interp, table, objv[i]);
        if (col == NULL) {
            Blt_Free(keys);
            return TCL_ERROR;
        }
        keys[numKeys] = col;
    }
    blt_table_set_keys(table, numKeys, keys, 0);
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * LappendOp --
 *
 *
 *      Appends one or more elements to the list at the given row, column
 *      location. If the column or row doesn't already exist, it will
 *      automatically be created.
 * 
 * Results:
 *      A standard TCL result. If the tag or index is invalid, TCL_ERROR is
 *      returned and an error message is left in the interpreter result.
 *      
 *      tableName append rowName colName ?value ...?
 *
 *---------------------------------------------------------------------------
 */
static int
LappendOp(ClientData clientData, Tcl_Interp *interp, int objc,
          Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    BLT_TABLE table;
    BLT_TABLE_ITERATOR ri, ci;
    BLT_TABLE_COLUMN col;

    table = cmdPtr->table;
    if (IterateRowsWithCreate(interp, table, objv[2], &ri) != TCL_OK) {
        return TCL_ERROR;
    }
    if (IterateColumnsWithCreate(interp, table, objv[3], &ci) != TCL_OK) {
        return TCL_ERROR;
    }
    for (col = blt_table_first_tagged_column(&ci); col != NULL; 
         col = blt_table_next_tagged_column(&ci)) {
        BLT_TABLE_ROW row;
        
        for (row = blt_table_first_tagged_row(&ri); row != NULL; 
             row = blt_table_next_tagged_row(&ri)) {
            Tcl_Obj *listObjPtr;
            int i, result;

            listObjPtr = blt_table_get_obj(table, row, col);
            if (listObjPtr == NULL) {
                listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL);
            }
            Tcl_IncrRefCount(listObjPtr);
            for (i = 4; i < objc; i++) {
                Tcl_ListObjAppendElement(interp, listObjPtr, objv[i]);
            }
            result = blt_table_set_obj(interp, table, row, col, listObjPtr);
            Tcl_DecrRefCount(listObjPtr);
            if (result != TCL_OK) {
                return TCL_ERROR;
            }
        }
    }       
    return TCL_OK;
}

static int
LookupOp(ClientData clientData, Tcl_Interp *interp, int objc,
         Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    int numKeys;
    BLT_TABLE_COLUMN *keys;
    BLT_TABLE_ROW row;
    BLT_TABLE table;
    long i;

    numKeys = blt_table_get_keys(cmdPtr->table, &keys);
    if ((objc - 2) != numKeys) {
        Tcl_AppendResult(interp, "wrong # of keys: should be \"", (char *)NULL);
        for (i = 0; i < numKeys; i++) {
            Tcl_AppendResult(interp, blt_table_column_label(keys[i]), " ", 
                             (char *)NULL);
        }
        Tcl_AppendResult(interp, "\"", (char *)NULL);
        return TCL_ERROR;
    }
    table = cmdPtr->table;
    if (blt_table_key_lookup(interp, table, objc-2, objv+2, &row) != TCL_OK) {
        return TCL_ERROR;
    }
    i = (row == NULL) ? -1 : blt_table_row_index(table, row);
    Blt_SetLongObj(Tcl_GetObjResult(interp), i);
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * MinMaxOp --
 *  
 *      tableName min colName
 *      tableName max colName 
 *
 *---------------------------------------------------------------------------
 */
static int
MinMaxOp(ClientData clientData, Tcl_Interp *interp, int objc,
         Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    BLT_TABLE table;
    Tcl_Obj *listObjPtr;
    const char *string;
    char c;
    int length;
    int flags;

#define GET_MIN         (1<<0)
#define GET_MAX         (1<<1)
    string = Tcl_GetStringFromObj(objv[1], &length);
    c = string[0];
    flags = 0;                          /* Suppress compiler warning. */
    if ((c == 'l') && (strncmp(string, "limits", length) == 0)) {
        flags = (GET_MIN | GET_MAX);
    } else if ((c == 'm') && (strncmp(string, "min", length) == 0)) {
        flags = GET_MIN;
    } else if ((c == 'm') && (strncmp(string, "max", length) == 0)) {
        flags = GET_MAX;
    }
    table = cmdPtr->table;
    listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL);
    if (objc == 2) {
        BLT_TABLE_COLUMN col;

        for (col = blt_table_first_column(table); col != NULL;
             col = blt_table_next_column(col)) {
            Tcl_Obj *minObjPtr, *maxObjPtr;

            if (blt_table_get_column_limits(interp, table, col, &minObjPtr, 
                                          &maxObjPtr) != TCL_OK) {
                return TCL_ERROR;
            }
            if (flags & GET_MIN) {
                Tcl_ListObjAppendElement(interp, listObjPtr, minObjPtr);
            } 
            if (flags & GET_MAX) {
                Tcl_ListObjAppendElement(interp, listObjPtr, maxObjPtr);
            }
        }
    } else {
        BLT_TABLE_ITERATOR ci;
        BLT_TABLE_COLUMN col;

        if (blt_table_iterate_columns(interp, table, objv[2], &ci) != TCL_OK) {
            return TCL_ERROR;
        }
        for (col = blt_table_first_tagged_column(&ci); col != NULL; 
             col = blt_table_next_tagged_column(&ci)) {
            Tcl_Obj *minObjPtr, *maxObjPtr;

            if (blt_table_get_column_limits(interp, table, col, &minObjPtr, 
                        &maxObjPtr) != TCL_OK) {
                return TCL_ERROR;
            }
            if (flags & GET_MIN) {
                Tcl_ListObjAppendElement(interp, listObjPtr, minObjPtr);
            } 
            if (flags & GET_MAX) {
                Tcl_ListObjAppendElement(interp, listObjPtr, maxObjPtr);
            }
        }
    }
    Tcl_SetObjResult(interp, listObjPtr);
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * NumColumnsOp --
 *  
 *      tableName numcolumns 
 *
 *---------------------------------------------------------------------------
 */
static int
NumColumnsOp(ClientData clientData, Tcl_Interp *interp, int objc,
             Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    BLT_TABLE table;

    table = cmdPtr->table;
    if (objc == 3) {
        long count;

        if (Blt_GetCountFromObj(interp, objv[2], COUNT_NNEG, &count) != TCL_OK){
            return TCL_ERROR;
        }
        if (count < blt_table_num_columns(table)) {
            BLT_TABLE_COLUMN col, next;

            for (col = blt_table_column(table, count); col != NULL; 
                 col = next) {
                next = blt_table_next_column(col);
                blt_table_delete_column(table, col);
            }
        } else if (count > blt_table_num_columns(table)) {
            size_t extra;

            extra = count - blt_table_num_columns(table);
            blt_table_extend_columns(interp, table, extra, NULL);
        }
    }
    Blt_SetLongObj(Tcl_GetObjResult(interp), blt_table_num_columns(table));
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * NumRowsOp --
 *  
 *      tableName numrows
 *
 *---------------------------------------------------------------------------
 */
static int
NumRowsOp(ClientData clientData, Tcl_Interp *interp, int objc,
          Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    BLT_TABLE table;

    table = cmdPtr->table;
    if (objc == 3) {
        long count;

        if (Blt_GetCountFromObj(interp, objv[2], COUNT_NNEG, &count) != TCL_OK){
            return TCL_ERROR;
        }
        if (count < blt_table_num_rows(table)) {
            BLT_TABLE_ROW row, next;

            for (row = blt_table_row(table, count); row != NULL; 
                 row = next) {
                next = blt_table_next_row(row);
                blt_table_delete_row(table, row);
            }
        } else if (count > blt_table_num_rows(table)) {
            size_t extra;

            extra = count - blt_table_num_rows(table);
            blt_table_extend_rows(interp, table, extra, NULL);
        }
    }
    Blt_SetLongObj(Tcl_GetObjResult(interp), blt_table_num_rows(table));
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * PackOp --
 *
 *      tableName pack
 *
 *---------------------------------------------------------------------------
 */
static int
PackOp(ClientData clientData, Tcl_Interp *interp, int objc,
       Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;

    blt_table_pack(cmdPtr->table);
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * ResetOp --
 *
 * tableName reset
 *
 *---------------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
ResetOp(ClientData clientData, Tcl_Interp *interp, int objc,
        Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;

    blt_table_clear(cmdPtr->table);
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * RestoreOp --
 *
 * tableName restore ?switches?
 *
 *---------------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
RestoreOp(ClientData clientData, Tcl_Interp *interp, int objc,
          Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    RestoreSwitches switches;
    int result;

    memset((char *)&switches, 0, sizeof(switches));
    if (Blt_ParseSwitches(interp, restoreSwitches, objc - 2, objv + 2, 
                &switches, BLT_SWITCH_DEFAULTS) < 0) {
        return TCL_ERROR;
    }
    result = TCL_ERROR;
    if ((switches.dataObjPtr != NULL) && (switches.fileObjPtr != NULL)) {
        Tcl_AppendResult(interp, "can't set both -file and -data switches",
                         (char *)NULL);
        goto error;
    }
    if (switches.dataObjPtr != NULL) {
        result = blt_table_restore(interp, cmdPtr->table, 
                Tcl_GetString(switches.dataObjPtr), switches.flags);
    } else if (switches.fileObjPtr != NULL) {
        result = blt_table_file_restore(interp, cmdPtr->table, 
                Tcl_GetString(switches.fileObjPtr), switches.flags);
    } else {
        Tcl_AppendResult(interp, "must set either -file and -data switch",
                         (char *)NULL);
    }
 error:
    Blt_FreeSwitches(restoreSwitches, &switches, 0);
    return result;
}

/*
 *---------------------------------------------------------------------------
 *
 * RowCopyOp --
 *
 *      Copies the specified rows to the table.  A different table may be
 *      selected as the source.
 * 
 * Results:
 *      A standard TCL result. If the tag or row index is invalid,
 *      TCL_ERROR is returned and an error message is left in the
 *      interpreter result.
 *
 *      tableName row copy destRow srcRow ?-table srcTable?
 *
 *---------------------------------------------------------------------------
 */
static int
RowCopyOp(ClientData clientData, Tcl_Interp *interp, int objc,
          Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    CopySwitches switches;
    BLT_TABLE srcTable, destTable;
    int result;
    BLT_TABLE_ROW srcRow, destRow;

    /* Process switches following the row names. */
    switches.flags = 0;
    switches.table = NULL;
    result = TCL_ERROR;
    if (Blt_ParseSwitches(interp, copySwitches, objc - 5, objv + 5, 
        &switches, BLT_SWITCH_DEFAULTS) < 0) {
        goto error;
    }
    srcTable = destTable = cmdPtr->table;
    if (switches.table != NULL) {
        srcTable = switches.table;
    }
    destRow = NULL;
    if ((switches.flags & COPY_NEW) == 0) {
        destRow = blt_table_get_row(interp, destTable, objv[3]);
    }
    if (destRow == NULL) {
        destRow = blt_table_create_row(interp, destTable,
                                       Tcl_GetString(objv[3]));
        if (destRow == NULL) {
            goto error;
        }
    }
    srcRow = blt_table_get_row(interp, srcTable, objv[4]);
    if (srcRow == NULL) {
        goto error;
    }
    if (CopyRow(interp, srcTable, destTable, srcRow, destRow) != TCL_OK) {
        goto error;
    }
    if ((switches.flags & COPY_NOTAGS) == 0) {
        CopyRowTags(srcTable, destTable, srcRow, destRow);
    }
    result = TCL_OK;
 error:
    Blt_FreeSwitches(copySwitches, &switches, 0);
    return result;
    
}

/*
 *---------------------------------------------------------------------------
 *
 * RowCreateOp --
 *
 *      Creates a single new row into the table.  The location of the new
 *      row may be specified by -before or -after switches.  By default the
 *      new row is added to to the end of the table.
 * 
 * Results:
 *      A standard TCL result. If the tag or row index is invalid,
 *      TCL_ERROR is returned and an error message is left in the
 *      interpreter result.
 *
 *      tableName row create -before 0 -after 1 -label label
 *      
 *---------------------------------------------------------------------------
 */
static int
RowCreateOp(ClientData clientData, Tcl_Interp *interp, int objc,
            Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    BLT_TABLE table;
    BLT_TABLE_ROW row;
    InsertRowSwitches switches;
    
    memset(&switches, 0, sizeof(switches));
    switches.cmdPtr = cmdPtr;
    table = cmdPtr->table;

    if (Blt_ParseSwitches(interp, insertRowSwitches, objc - 3, objv + 3, 
                &switches, 0) < 0) {
        goto error;
    }
    row = blt_table_create_row(interp, table, switches.label);
    if (row == NULL) {
        goto error;
    }
    if (switches.destRow != NULL) {
        if (blt_table_move_rows(interp, table, switches.destRow, row, row, 
                    switches.flags & INSERT_AFTER) != TCL_OK) {
            goto error;
        }
    }
    if (switches.tags != NULL) {
        Tcl_Obj **elv;
        int elc;
        int i;

        if (Tcl_ListObjGetElements(interp, switches.tags, &elc, &elv) 
            != TCL_OK) {
            goto error;
        }
        for (i = 0; i < elc; i++) {
            if (blt_table_set_row_tag(interp, table, row, Tcl_GetString(elv[i])) 
                != TCL_OK) {
                goto error;
            }
        }
    }
    Tcl_SetObjResult(interp, GetRowIndexObj(table, row));
    Blt_FreeSwitches(insertRowSwitches, &switches, 0);
    return TCL_OK;
 error:
    Blt_FreeSwitches(insertRowSwitches, &switches, 0);
    return TCL_ERROR;
}

/*
 *---------------------------------------------------------------------------
 *
 * RowDeleteOp --
 *
 *      Deletes the rows designated.  One or more rows may be deleted using
 *      a tag.
 * 
 * Results:
 *      A standard TCL result. If the tag or row index is invalid,
 *      TCL_ERROR is returned and an error message is left in the
 *      interpreter result.
 *
 *      tableName row delete ?rowName ...?
 *
 *---------------------------------------------------------------------------
 */
static int
RowDeleteOp(ClientData clientData, Tcl_Interp *interp, int objc,
            Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    BLT_TABLE_ITERATOR ri;
    BLT_TABLE_ROW row;
    int result;

    result = TCL_ERROR;
    if (blt_table_iterate_rows_objv(interp, cmdPtr->table, objc - 3, objv + 3, 
        &ri) != TCL_OK) {
        goto error;
    }
    for (row = blt_table_first_tagged_row(&ri); row != NULL; 
         row = blt_table_next_tagged_row(&ri)) {
        if (blt_table_delete_row(cmdPtr->table, row) != TCL_OK) {
            goto error;
        }
    }
    result = TCL_OK;
 error:
    blt_table_free_iterator_objv(&ri);
    return result;
}


/*
 *---------------------------------------------------------------------------
 *
 * RowDupOp --
 *
 *      Duplicates the specified rows in the table.  This differs from
 *      RowCopyOp, since the same table is always the source and destination.
 * 
 * Results:
 *      A standard TCL result. If the tag or row index is invalid, TCL_ERROR
 *      is returned and an error message is left in the interpreter result.
 *
 *      tableName row dup label ?label...? 
 *
 *---------------------------------------------------------------------------
 */
static int
RowDupOp(ClientData clientData, Tcl_Interp *interp, int objc,
         Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    Tcl_Obj *listObjPtr;
    BLT_TABLE_ITERATOR ri;
    BLT_TABLE_ROW srcRow;
    int result;

    if (blt_table_iterate_rows_objv(interp, cmdPtr->table, objc - 3, objv + 3, 
        &ri) != TCL_OK) {
        return TCL_ERROR;
    }
    result = TCL_ERROR;
    listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL);
    for (srcRow = blt_table_first_tagged_row(&ri); srcRow != NULL; 
         srcRow = blt_table_next_tagged_row(&ri)) {
        const char *label;
        size_t j;
        BLT_TABLE_ROW dest;

        label = blt_table_row_label(srcRow);
        dest = blt_table_create_row(interp, cmdPtr->table, label);
        if (dest == NULL) {
            goto error;
        }
        if (CopyRow(interp, cmdPtr->table, cmdPtr->table, srcRow, dest)!= TCL_OK) {
            goto error;
        }
        CopyRowTags(cmdPtr->table, cmdPtr->table, srcRow, dest);
        j = blt_table_row_index(cmdPtr->table, dest);
        Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewWideIntObj(j));
    }
    Tcl_SetObjResult(interp, listObjPtr);
    result = TCL_OK;
 error:
    blt_table_free_iterator_objv(&ri);
    if (result != TCL_OK) {
        Tcl_DecrRefCount(listObjPtr);
    }
    return result;
}

/*
 *---------------------------------------------------------------------------
 *
 * RowEmptyOp --
 *
 *      Returns a list of the columns with empty values in the given row.
 *
 * Results:
 *      A standard TCL result. If the tag or row index is invalid,
 *      TCL_ERROR is returned and an error message is left in the
 *      interpreter result.
 *
 *      tableName row empty rowName
 *      
 *---------------------------------------------------------------------------
 */
static int
RowEmptyOp(ClientData clientData, Tcl_Interp *interp, int objc,
           Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    BLT_TABLE_ROW row;
    Tcl_Obj *listObjPtr;
    size_t i;

    row = blt_table_get_row(interp, cmdPtr->table, objv[3]);
    if (row == NULL) {
        return TCL_ERROR;
    }
    listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL);
    for (i = 0; i < blt_table_num_rows(cmdPtr->table); i++) {
        BLT_TABLE_COLUMN col;
        
        for (col = blt_table_first_column(cmdPtr->table); col != NULL;
             col = blt_table_next_column(col)) {
            BLT_TABLE_VALUE value;

            value = blt_table_get_value(cmdPtr->table, row, col);
            if (value == NULL) {
                Tcl_Obj *objPtr;

                objPtr = GetColumnIndexObj(cmdPtr->table, col);
                Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
            }
        }
    }
    Tcl_SetObjResult(interp, listObjPtr);
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * RowExistsOp --
 *
 *      Indicates is the given row exists.
 * 
 * Results:
 *      A standard TCL result. If the tag or row index is invalid, TCL_ERROR
 *      is returned and an error message is left in the interpreter result.
 *
 *      tableName row exists rowName
 *      
 *---------------------------------------------------------------------------
 */
static int
RowExistsOp(ClientData clientData, Tcl_Interp *interp, int objc,
            Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    int bool;
    BLT_TABLE_ROW row;

    row = blt_table_get_row(NULL, cmdPtr->table, objv[3]);
    bool = (row != NULL);
    Tcl_SetBooleanObj(Tcl_GetObjResult(interp), bool);
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * RowExtendOp --
 *
 *      Extends the table by the given number of rows.
 * 
 * Results:
 *      A standard TCL result. If the tag or row index is invalid, TCL_ERROR
 *      is returned and an error message is left in the interpreter result.
 *
 *      tableName row extend numRows
 *      
 *---------------------------------------------------------------------------
 */
static int
RowExtendOp(ClientData clientData, Tcl_Interp *interp, int objc,
            Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    BLT_TABLE table;
    BLT_TABLE_ROW *rows;
    long numRows;
    ExtendSwitches switches;
    int result;
    
    memset(&switches, 0, sizeof(switches));
    if (Blt_ParseSwitches(interp, extendSwitches, objc - 4, objv + 4,
                &switches, BLT_SWITCH_DEFAULTS) < 0) {
        return TCL_ERROR;
    }
    table = cmdPtr->table;

    if (Blt_GetCountFromObj(interp, objv[3], COUNT_NNEG, &numRows) != TCL_OK) {
        return TCL_ERROR;
    }
    if (numRows == 0) {
        return TCL_OK;
    }
    rows = Blt_AssertMalloc(numRows * sizeof(BLT_TABLE_ROW));
    result = blt_table_extend_rows(interp, table, numRows, rows);
    if ((result == TCL_OK) && (switches.labels != NULL)) {
        size_t i;
        const char **p;
        
        for (i = 0, p = switches.labels; *p != NULL; p++, i++) {
            result = blt_table_set_row_label(interp, table, rows[i], *p);
            if (result != TCL_OK) {
                break;
            }
        }
    }
    if (result == TCL_OK) {
        size_t i;
        Tcl_Obj *listObjPtr;

        listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL);
        for (i = 0; i < numRows; i++) {
            Tcl_Obj *objPtr;
            
            objPtr = GetRowIndexObj(table, rows[i]);
            Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
        }
        Tcl_SetObjResult(interp, listObjPtr);
    }
    Blt_Free(rows);
    Blt_FreeSwitches(extendSwitches, &switches, 0);
    return result;
}

/*
 *---------------------------------------------------------------------------
 *
 * RowGetOp --
 *
 *      Retrieves the values from a given row.  The row argument can be either
 *      a tag, label, or row index.  If it is a tag, it must refer to exactly
 *      one row.  An optional argument specifies how to return empty values.
 *      By default, the global empty value representation is used.
 * 
 * Results:
 *      A standard TCL result.  If successful, a list of values is returned in
 *      the interpreter result.  If the row index is invalid, TCL_ERROR is
 *      returned and an error message is left in the interpreter result.
 *      
 *      tableName row get ?-labels? rowName ?colName...?
 *
 *---------------------------------------------------------------------------
 */
static int
RowGetOp(ClientData clientData, Tcl_Interp *interp, int objc,
         Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    Tcl_Obj *listObjPtr;
    BLT_TABLE_ROW row;
    BLT_TABLE table;
    const char *string;
    int needLabels;

    string = Tcl_GetString(objv[3]);
    needLabels = FALSE;
    if (strcmp(string, "-labels") == 0) {
        objv++, objc--;
        needLabels = TRUE;
    }
    table = cmdPtr->table;
    row = blt_table_get_row(interp, table, objv[3]);
    if (row == NULL) {
        return TCL_ERROR;
    }
    listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL);
    if (objc == 4) {
        BLT_TABLE_COLUMN col;

        for (col = blt_table_first_column(table); col != NULL;
             col = blt_table_next_column(col)) {
            Tcl_Obj *objPtr;
            
            if (needLabels) {
                objPtr = Tcl_NewStringObj(blt_table_column_label(col), -1);
            } else {
                objPtr = GetColumnIndexObj(table, col);
            }
            Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
            objPtr = blt_table_get_obj(table, row, col);
            if (objPtr == NULL) {
                objPtr = Tcl_NewStringObj(cmdPtr->emptyString, -1);
            }
            Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
        }
    } else {
        BLT_TABLE_ITERATOR ci;
        BLT_TABLE_COLUMN col;

        if (blt_table_iterate_columns_objv(interp, table, objc - 4, objv + 4, 
                &ci) != TCL_OK) {
            return TCL_ERROR;
        }
        for (col = blt_table_first_tagged_column(&ci); col != NULL; 
             col = blt_table_next_tagged_column(&ci)) {
            Tcl_Obj *objPtr;
            
            if (needLabels) {
                objPtr = Tcl_NewStringObj(blt_table_column_label(col), -1);
            } else {
                objPtr = GetColumnIndexObj(table, col);
            }
            Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
            objPtr = blt_table_get_obj(table, row, col);
            if (objPtr == NULL) {
                objPtr = Tcl_NewStringObj(cmdPtr->emptyString, -1);
            }
            Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
        }
        blt_table_free_iterator_objv(&ci);
    }
    Tcl_SetObjResult(interp, listObjPtr);
    return TCL_OK;
}


/*
 *---------------------------------------------------------------------------
 *
 * RowIndexOp --
 *
 *      Returns the row index of the given row tag, label, or index.  A tag
 *      can't represent more than one row.
 * 
 * Results:
 *      A standard TCL result. If the tag or row index is invalid, TCL_ERROR
 *      is returned and an error message is left in the interpreter result.
 *
 *      tableName row index rowName
 *      
 *---------------------------------------------------------------------------
 */
static int
RowIndexOp(ClientData clientData, Tcl_Interp *interp, int objc,
           Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    BLT_TABLE_ITERATOR iter;
    long index;

    index = -1;
    if (blt_table_iterate_rows(interp, cmdPtr->table, objv[3], &iter)
        == TCL_OK) {
        BLT_TABLE_ROW row;

        row = blt_table_first_tagged_row(&iter);
        if (row != NULL) {
            BLT_TABLE_ROW next;
            
            index = blt_table_row_index(cmdPtr->table, row);
            next = blt_table_next_tagged_row(&iter);
            if (next != NULL) {
                /* It's not an error to look for an index of a row that
                 * doesn't exist. Duplicate labels are another story. This
                 * is too subtle a problem. Better to error on
                 * duplicates.  */
                const char *tag;
            
                blt_table_row_spec(cmdPtr->table, objv[3], &tag);
                Tcl_AppendResult(interp, "multiple rows specified by \"", 
                                 tag, "\"", (char *)NULL);
                return TCL_ERROR;
            }
        }
    }
    Blt_SetLongObj(Tcl_GetObjResult(interp), index);
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * RowIndicesOp --
 *
 *      Returns a list of indices for the given rows.  
 * 
 * Results:
 *      A standard TCL result. If the tag or row index is invalid, TCL_ERROR
 *      is returned and an error message is left in the interpreter result.
 *
 *      tableName row indices rowName ?rowName ...?
 *      
 *---------------------------------------------------------------------------
 */
static int
RowIndicesOp(ClientData clientData, Tcl_Interp *interp, int objc,
             Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    BLT_TABLE_ITERATOR ri;
    BLT_TABLE_ROW row;
    Tcl_Obj *listObjPtr;

    if (blt_table_iterate_rows_objv(interp, cmdPtr->table, objc - 3, objv + 3, 
        &ri) != TCL_OK) {
        return TCL_ERROR;
    }
    listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL);
    for (row = blt_table_first_tagged_row(&ri); row != NULL; 
         row = blt_table_next_tagged_row(&ri)) {
        Tcl_Obj *objPtr;

        objPtr = GetRowIndexObj(cmdPtr->table, row);
        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
    }
    Tcl_SetObjResult(interp, listObjPtr);
    blt_table_free_iterator_objv(&ri);
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * RowIsNumeric --
 *
 *      Returns a whether all the entries in the row are numeric.
 * 
 * Results:
 *      A standard TCL result. 
 *
 *      tableName row isnumeric rowName
 *      
 *---------------------------------------------------------------------------
 */
static int
RowIsNumericOp(ClientData clientData, Tcl_Interp *interp, int objc,
               Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    BLT_TABLE_COLUMN col;
    BLT_TABLE_ROW row;
    int state;
    
    row = blt_table_get_row(interp, cmdPtr->table, objv[3]);
    if (row == NULL) {
        return TCL_ERROR;
    }
    state = TRUE;
    for (col = blt_table_first_column(cmdPtr->table); col != NULL; 
         col = blt_table_next_column(col)) {
        double d;
        
        d = blt_table_get_double(interp, cmdPtr->table, row, col);
        if (!FINITE(d)) {
            state = FALSE;
            break;
        }
    }
    Tcl_SetBooleanObj(Tcl_GetObjResult(interp), state);
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * RowIsHeaderOp --
 *
 *      Returns a whether all the entries in the row are headers (column
 *      labels).  We test first if the value starts with a number and then
 *      if the value is unique.  Column header labels should ordinarily not
 *      start with a number or be duplicated.
 * 
 * Results:
 *      A standard TCL result. 
 *
 *      tableName row isalpha rowName
 *      
 *---------------------------------------------------------------------------
 */
static int
RowIsHeaderOp(ClientData clientData, Tcl_Interp *interp, int objc,
              Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    BLT_TABLE_COLUMN col;
    BLT_TABLE_ROW row;
    int state;
    Blt_HashTable table;
    
    row = blt_table_get_row(interp, cmdPtr->table, objv[3]);
    if (row == NULL) {
        return TCL_ERROR;
    }
    Blt_InitHashTable(&table, BLT_STRING_KEYS);
    state = TRUE;
    for (col = blt_table_first_column(cmdPtr->table); col != NULL; 
         col = blt_table_next_column(col)) {
        const char *value;
        int isNew;
        
        value = blt_table_get_string(cmdPtr->table, row, col);
        if (value == NULL) {
            continue;                   /* Ignore empty cells. */
        }
        if (isdigit(value[0])) {
            state = FALSE;              /* Can't start with a number. */
            break;
        }
        Blt_CreateHashEntry(&table, value, &isNew);
        if (!isNew) {
            state = FALSE;              /* Must be unique. */
            break;
        }
    }
    Blt_DeleteHashTable(&table);
    Tcl_SetBooleanObj(Tcl_GetObjResult(interp), state);
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * RowJoinOp --
 *
 *      Joins the rows of the source table onto the destination.
 * 
 * Results:
 *      A standard TCL result. If the tag or column index is invalid,
 *      TCL_ERROR is returned and an error message is left in the interpreter
 *      result.
 *
 *      destTable row join srcTable -rows rowList
 *
 *---------------------------------------------------------------------------
 */
static int
RowJoinOp(ClientData clientData, Tcl_Interp *interp, int objc,
          Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    JoinSwitches switches;
    BLT_TABLE srcTable;
    size_t oldDstLen, count;
    int result;
    BLT_TABLE_COLUMN srcCol;
    const char *srcName;

    srcName = Tcl_GetString(objv[3]);
    if (blt_table_open(interp, srcName, &srcTable) != TCL_OK) {
        return TCL_ERROR;
    }
    result = TCL_ERROR;
    /* Process switches following the column names. */
    rowIterSwitch.clientData = srcTable;
    switches.flags = 0;
    blt_table_iterate_all_rows(srcTable, &switches.ri);
    if (Blt_ParseSwitches(interp, joinSwitches, objc - 4, objv + 4, &switches, 
        BLT_SWITCH_DEFAULTS | JOIN_ROW) < 0) {
        goto error;
    }
    oldDstLen = blt_table_num_rows(cmdPtr->table);
    count = switches.ri.numEntries;
    if (blt_table_extend_rows(interp, cmdPtr->table, count, NULL)
        != TCL_OK) {
        goto error;
    }
    for (srcCol = blt_table_first_column(srcTable); srcCol != NULL; 
         srcCol = blt_table_next_column(srcCol)) {
        const char *label;
        BLT_TABLE_COLUMN dstCol;
        BLT_TABLE_ROW srcRow;
        size_t i;

        label = blt_table_column_label(srcCol);
        dstCol = blt_table_get_column_by_label(cmdPtr->table, label);
        if (dstCol == NULL) {

            /* If column doesn't exist in destination table, create a new
             * column, copying the label and the column type. */

            if (blt_table_extend_columns(interp, cmdPtr->table, 1, &dstCol) 
                != TCL_OK) {
                goto error;
            }
            if (blt_table_set_column_label(interp, cmdPtr->table, dstCol, label) 
                != TCL_OK) {
                goto error;
            }
            if (blt_table_set_column_type(interp, cmdPtr->table, dstCol, 
                blt_table_column_type(srcCol)) != TCL_OK) {
                goto error;
            }
        }
        i = oldDstLen;
        for (srcRow = blt_table_first_tagged_row(&switches.ri); srcRow != NULL; 
             srcRow = blt_table_next_tagged_row(&switches.ri)) {
            BLT_TABLE_VALUE value;
            BLT_TABLE_ROW dstRow;

            value = blt_table_get_value(srcTable, srcRow, srcCol);
            if (value == NULL) {
                continue;
            }
            dstRow = blt_table_row(cmdPtr->table, i);
            i++;
            if (blt_table_set_value(cmdPtr->table, dstRow, dstCol, value) 
                != TCL_OK) {
                goto error;
            }
        }
        if ((switches.flags & COPY_NOTAGS) == 0) {
            CopyColumnTags(srcTable, cmdPtr->table, srcCol, dstCol);
        }
    }
    result = TCL_OK;
 error:
    blt_table_close(srcTable);
    Blt_FreeSwitches(addSwitches, &switches, JOIN_ROW);
    return result;
}

/*
 *---------------------------------------------------------------------------
 *
 * RowLabelOp --
 *
 *      Gets/sets a label for one or more rows.  
 * 
 * Results:
 *      A standard TCL result.  If successful, the old row label is returned
 *      in the interpreter result.  If the row index is invalid, TCL_ERROR is
 *      returned and an error message is left in the interpreter result.
 *      
 *      tableName row label rowName ?label? ?rowName label? 
 *
 *---------------------------------------------------------------------------
 */
static int
RowLabelOp(ClientData clientData, Tcl_Interp *interp, int objc,
           Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    BLT_TABLE table;

    table = cmdPtr->table;
    if (objc == 4) {
        const char *label;
        BLT_TABLE_ROW row;

        row = blt_table_get_row(interp, table, objv[3]);
        if (row == NULL) {
            return TCL_ERROR;
        }
        label = blt_table_row_label(row);
        Tcl_SetStringObj(Tcl_GetObjResult(interp), label, -1);
    } else {
        int i;
        
        if ((objc - 3) & 1) {
            Tcl_AppendResult(interp,"odd # of row/label pairs: should be \"",
                Tcl_GetString(objv[0]), " ?rowName label ...?", 
                        (char *)NULL);
            return TCL_ERROR;
        }
        for (i = 3; i < objc; i += 2) {
            BLT_TABLE_ROW row;
            const char *label;

            row = blt_table_get_row(interp, table, objv[i]);
            if (row == NULL) {
                return TCL_ERROR;
            }
            label = Tcl_GetString(objv[i+1]);
            if (blt_table_set_row_label(interp, table, row, label) != TCL_OK) {
                return TCL_ERROR;
            }
        }
    }
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * RowLabelsOp --
 *
 *      Gets/sets all the row label in the table.  
 * 
 * Results:
 *      A standard TCL result.  If successful, a list of values is returned in
 *      the interpreter result.  If the row index is invalid, TCL_ERROR is
 *      returned and an error message is left in the interpreter result.
 *      
 *      tableName row labels ?labelList?
 *
 *---------------------------------------------------------------------------
 */
static int
RowLabelsOp(ClientData clientData, Tcl_Interp *interp, int objc,
            Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    BLT_TABLE table;

    table = cmdPtr->table;
    if (objc == 3) {
        BLT_TABLE_ROW row;
        Tcl_Obj *listObjPtr;

        listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL);
        for (row = blt_table_first_row(table); row != NULL;
             row = blt_table_next_row(row)) {
            Tcl_Obj *objPtr;
            
            objPtr = Tcl_NewStringObj(blt_table_row_label(row), -1);
            Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
        }
        Tcl_SetObjResult(interp, listObjPtr);
    } else if (objc == 4) {
        Tcl_Obj **elv;
        int elc, n;
        int i;
        BLT_TABLE_ROW row;

        if (Tcl_ListObjGetElements(interp, objv[3], &elc, &elv) != TCL_OK) {
            return TCL_ERROR;
        }
        n = MIN(elc, blt_table_num_rows(table));
        for (i = 0, row = blt_table_first_row(table); (row != NULL) && (i < n); 
             row = blt_table_next_row(row), i++) {
            const char *label;

            label = Tcl_GetString(elv[i]);
            if (blt_table_set_row_label(interp, table, row, label) != TCL_OK) {
                return TCL_ERROR;
            }
        }
    }
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * RowMoveOp --
 *
 *      Moves the given number of rows to another location in the table.
 * 
 * Results:
 *      A standard TCL result.  If successful, a list of values is returned in
 *      the interpreter result.  If the row index is invalid, TCL_ERROR is
 *      returned and an error message is left in the interpreter result.
 *      
 *      tableName row move destRow firstRow lastRow ?switches...?
 *
 *---------------------------------------------------------------------------
 */
static int
RowMoveOp(ClientData clientData, Tcl_Interp *interp, int objc,
          Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    MoveSwitches switches;
    BLT_TABLE_ROW destRow, firstRow, lastRow;

    destRow = blt_table_get_row(interp, cmdPtr->table, objv[3]);
    if (destRow == NULL) {
        return TCL_ERROR;
    }
    firstRow = blt_table_get_row(interp, cmdPtr->table, objv[4]);
    if (firstRow == NULL) {
        return TCL_ERROR;
    }
    lastRow = blt_table_get_row(interp, cmdPtr->table, objv[5]);
    if (lastRow == NULL) {
        return TCL_ERROR;
    }
    /* Check if range is valid. */
    if ((blt_table_row_index(cmdPtr->table, firstRow) > 
         blt_table_row_index(cmdPtr->table, lastRow))) {
        return TCL_OK;                  /* No range. */
    }

    /* Check that destination is outside the range of rows to be moved. */
    if ((blt_table_row_index(cmdPtr->table, destRow) >= 
         blt_table_row_index(cmdPtr->table, firstRow)) &&
        (blt_table_row_index(cmdPtr->table, destRow) <= 
         blt_table_row_index(cmdPtr->table, lastRow))) {
        Tcl_AppendResult(interp, "destination row \"", Tcl_GetString(objv[3]),
                 "\" can't be in the range of rows to be moved", 
                (char *)NULL);
        return TCL_ERROR;
    }
    memset(&switches, 0, sizeof(switches));
    if (Blt_ParseSwitches(interp, moveSwitches, objc - 6, objv + 6, 
        &switches, BLT_SWITCH_DEFAULTS) < 0) {
        return TCL_ERROR;
    }
    return blt_table_move_rows(interp, cmdPtr->table, destRow, firstRow, 
        lastRow, switches.flags & MOVE_AFTER);
}


/*
 *---------------------------------------------------------------------------
 *
 * RowNamesOp --
 *
 *      Reports the labels of all rows.  
 * 
 * Results:
 *      Always returns TCL_OK.  The interpreter result is a list of row
 *      labels.
 *      
 *      tableName row names ?pattern...?
 *
 *---------------------------------------------------------------------------
 */
static int
RowNamesOp(ClientData clientData, Tcl_Interp *interp, int objc,
           Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    BLT_TABLE table;
    Tcl_Obj *listObjPtr;
    BLT_TABLE_ROW row;

    table = cmdPtr->table;
    listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL);
    for (row = blt_table_first_row(table); row != NULL;
         row = blt_table_next_row(row)) {
        const char *label;
        int match;
        int i;

        label = blt_table_row_label(row);
        match = (objc == 3);
        for (i = 3; i < objc; i++) {
            const char *pattern;

            pattern = Tcl_GetString(objv[i]);
            if (Tcl_StringMatch(label, pattern)) {
                match = TRUE;
                break;
            }
        }
        if (match) {
            Tcl_Obj *objPtr;

            objPtr = Tcl_NewStringObj(label, -1);
            Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
        }
    }
    Tcl_SetObjResult(interp, listObjPtr);
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * RowNonEmptyOp --
 *
 *      Returns a list of the columns with non-empty values in the given row.
 *
 * Results:
 *      A standard TCL result. If the tag or row index is invalid,
 *      TCL_ERROR is returned and an error message is left in the interpreter
 *      result.
 *
 *      tableName row nonempty row
 *      
 *---------------------------------------------------------------------------
 */
static int
RowNonEmptyOp(ClientData clientData, Tcl_Interp *interp, int objc,
              Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    BLT_TABLE_ROW row;
    Tcl_Obj *listObjPtr;
    size_t i;

    row = blt_table_get_row(interp, cmdPtr->table, objv[3]);
    if (row == NULL) {
        return TCL_ERROR;
    }
    listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL);
    for (i = 0; i < blt_table_num_rows(cmdPtr->table); i++) {
        BLT_TABLE_COLUMN col;
        
        for (col = blt_table_first_column(cmdPtr->table); col != NULL;
             col = blt_table_next_column(col)) {
            BLT_TABLE_VALUE value;

            value = blt_table_get_value(cmdPtr->table, row, col);
            if (value != NULL) {
                Tcl_Obj *objPtr;

                objPtr = GetColumnIndexObj(cmdPtr->table, col);
                Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
            }
        }
    }
    Tcl_SetObjResult(interp, listObjPtr);
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * RowReorderOp --
 *
 *      Reorders the table according to give TCL list of rows.
 *
 * Results:
 *      A standard TCL result. 
 *
 *      tableName row reorder rowList
 *      
 *---------------------------------------------------------------------------
 */
static int
RowReorderOp(ClientData clientData, Tcl_Interp *interp, int objc,
             Tcl_Obj *const *objv)
{
    BLT_TABLE_ROW *map;
    Cmd *cmdPtr = clientData;
    Tcl_Obj **elv;
    int elc;
    size_t i;
    
    if (Tcl_ListObjGetElements(interp, objv[3], &elc, &elv) != TCL_OK) {
        return TCL_ERROR;
    }
    if (elc != blt_table_num_rows(cmdPtr->table)) {
        Tcl_AppendResult(interp,
            "# of elements in the row list does not match the # of rows",
            (char *)NULL);
        return TCL_ERROR;
    }
    for (i = 0; i < elc; i++) {
        BLT_TABLE_ROW row;
        
        row = blt_table_get_row(interp, cmdPtr->table, elv[i]);
        if (row == NULL) {
            return TCL_ERROR;
        }
    }
    map = Blt_AssertCalloc(elc, sizeof(BLT_TABLE_ROW));
    for (i = 0; i < elc; i++) {
        map[i] = blt_table_get_row(interp, cmdPtr->table, elv[i]);
    }
    blt_table_set_row_map(cmdPtr->table, map);
    return TCL_OK;
}


/*
 *---------------------------------------------------------------------------
 *
 * RowSetOp --
 *
 *      Sets a row of values.  One or more rows may be set using a tag.  The
 *      column order is always the table's current view of the table.  There
 *      may be less values than needed.
 * 
 * Results:
 *      A standard TCL result. If the tag or row index is invalid, TCL_ERROR
 *      is returned and an error message is left in the interpreter result.
 *      
 *      tableName row set rowName ?switches? ?colName value ...?
 *
 *---------------------------------------------------------------------------
 */
static int
RowSetOp(ClientData clientData, Tcl_Interp *interp, int objc,
         Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    BLT_TABLE table;
    BLT_TABLE_ITERATOR ri;
    size_t numCols;
    BLT_TABLE_ROW row;

    table = cmdPtr->table;
    /* May set more than one row with the same values. */
    if (IterateRowsWithCreate(interp, table, objv[3], &ri) != TCL_OK) {
        return TCL_ERROR;
    }
    if (objc == 4) {
        return TCL_OK;
    }
    numCols = objc - 4;
    if (numCols & 1) {
        Tcl_AppendResult(interp, "odd # of column/value pairs: should be \"", 
                Tcl_GetString(objv[0]), " row set column value...", 
                         (char *)NULL);
        return TCL_ERROR;
    }
    for (row = blt_table_first_tagged_row(&ri); row != NULL; 
         row = blt_table_next_tagged_row(&ri)) {
        size_t i;

        /* The remaining arguments are index/value pairs. */
        for (i = 4; i < objc; i += 2) {
            BLT_TABLE_COLUMN col;
            
            col = blt_table_get_column(interp, table, objv[i]);
            if (col == NULL) {
                /* Can't find the column. Create it and try to find it
                 * again. */
                if (MakeColumns(interp, table, objv[i]) != TCL_OK) {
                    return TCL_ERROR;
                }
                col = blt_table_get_column(interp, table, objv[i]);
            }
            if (blt_table_set_obj(interp, table, row, col, objv[i + 1])
                != TCL_OK) {
                return TCL_ERROR;
            }
        }
    }
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * RowTagAddOp --
 *
 *      Adds a given tag to one or more rows.  Tag names can't start with a
 *      digit (to distinquish them from node ids) and can't be a reserved
 *      tag ("all" or "end").
 *
 *      tableName row tag add tagName ?rowName ...?
 *
 *---------------------------------------------------------------------------
 */
static int
RowTagAddOp(ClientData clientData, Tcl_Interp *interp, int objc,
            Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    BLT_TABLE table;
    int i;
    const char *tag;

    table = cmdPtr->table;
    tag = Tcl_GetString(objv[4]);

    /* Create an empty row tag. */
    if (blt_table_set_row_tag(interp, table, NULL, tag) != TCL_OK) {
        return TCL_ERROR;
    }
    for (i = 5; i < objc; i++) {
        BLT_TABLE_ROW row;
        BLT_TABLE_ITERATOR ri;

        if (blt_table_iterate_rows(interp, table, objv[i], &ri) != TCL_OK) {
            return TCL_ERROR;
        }
        for (row = blt_table_first_tagged_row(&ri); row != NULL; 
             row = blt_table_next_tagged_row(&ri)) {
            if (blt_table_set_row_tag(interp, table, row, tag) != TCL_OK) {
                return TCL_ERROR;
            }
        }    
    }
    return TCL_OK;
}


/*
 *---------------------------------------------------------------------------
 *
 * RowTagDeleteOp --
 *
 *      Removes a given tag from one or more rows. If a tag doesn't exist
 *      or is a reserved tag ("all" or "end"), nothing will be done and no
 *      error message will be returned.
 *
 *      tableName row tag delete tagName ?rowName ...?
 *
 *---------------------------------------------------------------------------
 */
static int
RowTagDeleteOp(ClientData clientData, Tcl_Interp *interp, int objc,
               Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    BLT_TABLE table;
    BLT_TABLE_ITERATOR ri;
    int i;
    const char *tag;

    table = cmdPtr->table;
    tag = Tcl_GetString(objv[4]);
    for (i = 5; i < objc; i++) {
        BLT_TABLE_ROW row;
        
        if (blt_table_iterate_rows(interp, table, objv[i], &ri) != TCL_OK) {
            return TCL_ERROR;
        }
        for (row = blt_table_first_tagged_row(&ri); row != NULL; 
             row = blt_table_next_tagged_row(&ri)) {
            if (blt_table_unset_row_tag(table, row, tag) != TCL_OK) {
                return TCL_ERROR;
            }
        }
    }
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * RowTagExistsOp --
 *
 *      Returns the existence of a tag in the table.  If a row is specified
 *      then the tag is search for for that row.
 *
 *      table tag row exists tagName ?rowName ...?
 *
 *---------------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
RowTagExistsOp(ClientData clientData, Tcl_Interp *interp, int objc,
               Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    int bool;
    const char *tag;

    tag = Tcl_GetString(objv[4]);
    bool = (blt_table_get_tagged_rows(cmdPtr->table, tag) != NULL);
    if (objc == 6) {
        BLT_TABLE_ROW row;

        row = blt_table_get_row(interp, cmdPtr->table, objv[5]);
        if (row == NULL) {
            bool = FALSE;
        } else {
            bool = blt_table_row_has_tag(cmdPtr->table, row, tag);
        }
    } 
    Tcl_SetBooleanObj(Tcl_GetObjResult(interp), bool);
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * RowTagForgetOp --
 *
 *      Removes the given tags from all nodes.
 *
 *      tableName row tag forget ?tagName ...?
 *
 *---------------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
RowTagForgetOp(ClientData clientData, Tcl_Interp *interp, int objc,
               Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    BLT_TABLE table;
    int i;

    table = cmdPtr->table;
    for (i = 4; i < objc; i++) {
        if (blt_table_forget_row_tag(table, Tcl_GetString(objv[i])) != TCL_OK) {
            return TCL_ERROR;
        }
    }
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * RowTagGetOp --
 *
 *      Returns the tag names for a given row.  If one of more pattern
 *      arguments are provided, then only those matching tags are returned.
 *
 *      tableName row tag get rowName ?pattern ...?
 *
 *---------------------------------------------------------------------------
 */
static int
RowTagGetOp(ClientData clientData, Tcl_Interp *interp, int objc,
            Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    Blt_HashEntry *hPtr;
    Blt_HashSearch hsearch;
    Blt_HashTable tagTable;
    BLT_TABLE table;
    BLT_TABLE_ITERATOR ri;
    BLT_TABLE_ROW row;
    Tcl_Obj *listObjPtr;

    table = cmdPtr->table;
    if (blt_table_iterate_rows(interp, table, objv[4], &ri) != TCL_OK) {
        return TCL_ERROR;
    }
    listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL);

    Blt_InitHashTable(&tagTable, BLT_STRING_KEYS);
    
    /* Collect all the tags into a hash table. */
    for (row = blt_table_first_tagged_row(&ri); row != NULL; 
         row = blt_table_next_tagged_row(&ri)) {
        Blt_Chain chain;
        Blt_ChainLink link;

        chain = blt_table_get_row_tags(table, row);
        for (link = Blt_Chain_FirstLink(chain); link != NULL;
             link = Blt_Chain_NextLink(link)) {
            const char *tag;
            int isNew;

            tag = Blt_Chain_GetValue(link);
            Blt_CreateHashEntry(&tagTable, tag, &isNew);
        }
        Blt_Chain_Destroy(chain);
    }
    for (hPtr = Blt_FirstHashEntry(&tagTable, &hsearch); hPtr != NULL;
         hPtr = Blt_NextHashEntry(&hsearch)) {
        int match;
        const char *tag;

        tag = Blt_GetHashKey(&tagTable, hPtr);
        match = TRUE;
        if (objc > 5) {
            int i;

            match = FALSE;
            for (i = 5; i < objc; i++) {
                if (Tcl_StringMatch(tag, Tcl_GetString(objv[i]))) {
                    match = TRUE;
                }
            }
        }
        if (match) {
            Tcl_Obj *objPtr;

            objPtr = Tcl_NewStringObj(tag, -1);
            Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
        }
    }
    Blt_DeleteHashTable(&tagTable);
    Tcl_SetObjResult(interp, listObjPtr);
    return TCL_OK;
}

static unsigned char *
GetRowTagMatches(BLT_TABLE table, int objc, Tcl_Obj *const *objv)
{
    size_t numRows;
    int i;
    unsigned char *matches;

    numRows = blt_table_num_rows(table);
    matches = Blt_AssertCalloc(numRows, sizeof(unsigned char));
    /* Handle the reserved tags "all" or "end". */
    for (i = 0; i < objc; i++) {
        char *tag;

        tag = Tcl_GetString(objv[i]);
        if (strcmp("all", tag) == 0) {
            size_t j;
            for (j = 0; j < blt_table_num_rows(table); j++) {
                matches[j] = TRUE;
            }
            return matches;             /* Don't care about other tags. */
        } 
        if ((numRows > 0) && (strcmp("end", tag) == 0)) {
            matches[numRows - 1] = TRUE;
        }
    }
    /* Now check user-defined tags. */
    for (i = 0; i < objc; i++) {
        Blt_Chain chain;
        Blt_ChainLink link;
        const char *tag;
        
        tag = Tcl_GetString(objv[i]);
        if ((strcmp("all", tag) == 0) || (strcmp("end", tag) == 0)) {
            continue;
        }
        chain = blt_table_get_tagged_rows(table, tag);
        if (chain == NULL) {
            Blt_Free(matches);
            return NULL;
        }
        for (link = Blt_Chain_FirstLink(chain); link != NULL; 
             link = Blt_Chain_NextLink(link)) {
            BLT_TABLE_ROW row;
            size_t j;
            
            row = Blt_Chain_GetValue(link);
            j = blt_table_row_index(table, row);
            matches[j] = TRUE;
        }
    }
    return matches;
}

/*
 *---------------------------------------------------------------------------
 *
 * RowTagIndicesOp --
 *
 *      Returns row indices names for the given tags.  If one of more tag
 *      names are provided, then only those matching indices are returned.
 *
 *      tableName row tag indices ?tagName ...?
 *
 *---------------------------------------------------------------------------
 */
static int
RowTagIndicesOp(ClientData clientData, Tcl_Interp *interp, int objc,
                Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    Tcl_Obj *listObjPtr;
    unsigned char *matches;

    matches = GetRowTagMatches(cmdPtr->table, objc - 4, objv + 4);
    listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL);
    if (matches != NULL) {
        size_t j;

        for (j = 0; j < blt_table_num_rows(cmdPtr->table); j++) {
            if (matches[j]) {
                Tcl_ListObjAppendElement(interp, listObjPtr,
                                         Tcl_NewWideIntObj(j));
            }
        }
        Blt_Free(matches);
    }
    Tcl_SetObjResult(interp, listObjPtr);
    return TCL_OK;
}
/*
 *---------------------------------------------------------------------------
 *
 * RowTagLabelsOp --
 *
 *      Returns row labels for the given tags.  If one of more tag
 *      names are provided, then only those matching indices are returned.
 *
 *      tableName row tag labels ?tagName ...?
 *
 *---------------------------------------------------------------------------
 */
static int
RowTagLabelsOp(ClientData clientData, Tcl_Interp *interp, int objc,
               Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    Tcl_Obj *listObjPtr;
    unsigned char *matches;

    matches = GetRowTagMatches(cmdPtr->table, objc - 4, objv + 4);
    listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL);
    if (matches != NULL) {
        BLT_TABLE_ROW row;

        for (row = blt_table_first_row(cmdPtr->table); row != NULL;
             row = blt_table_next_row(row)) {
            if (matches[blt_table_row_index(cmdPtr->table, row)]) {
                Tcl_Obj *objPtr;
                
                objPtr = Tcl_NewStringObj(blt_table_row_label(row), -1);
                Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
            }
        }
        Blt_Free(matches);
    }
    Tcl_SetObjResult(interp, listObjPtr);
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * RowTagRangeOp --
 *
 *      Adds one or more tags for a given row.  Tag names can't start with
 *      a digit (to distinquish them from node ids) and can't be a reserved
 *      tag ("all" or "end").
 *
 *      tableName row tag range fromRow toRow ?tagName ...?
 *
 *---------------------------------------------------------------------------
 */
static int
RowTagRangeOp(ClientData clientData, Tcl_Interp *interp, int objc,
              Tcl_Obj *const *objv)
{
    BLT_TABLE table;
    BLT_TABLE_ROW from, to;
    Cmd *cmdPtr = clientData;
    int i;

    table = cmdPtr->table;
    from = blt_table_get_row(interp, table, objv[4]);
    if (from == NULL) {
        return TCL_ERROR;
    }
    to = blt_table_get_row(interp, table, objv[5]);
    if (to == NULL) {
        return TCL_ERROR;
    }
    if (blt_table_row_index(table, from) > blt_table_row_index(table, to)) {
        return TCL_OK;
    }
    for (i = 6; i < objc; i++) {
        BLT_TABLE_ROW row;
        const char *tag;
        
        tag = Tcl_GetString(objv[i]);
        for (row = from; /*empty*/; row = blt_table_next_row(row)) {
            if (blt_table_set_row_tag(interp, table, row, tag) != TCL_OK) {
                return TCL_ERROR;
            }
            if (row == to) {
                break;
            }
        }    
    }
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * RowTagNamesOp --
 *
 *      Returns tag names for a given row.  If one of more pattern
 *      arguments are provided, then only those matching tags are returned.
 *
 *      tableName row tag names ?pattern ...?
 *
 *---------------------------------------------------------------------------
 */
static int
RowTagNamesOp(ClientData clientData, Tcl_Interp *interp, int objc,
              Tcl_Obj *const *objv)
{
    Blt_HashTable *tablePtr;
    Blt_HashEntry *hPtr;
    Blt_HashSearch iter;
    Cmd *cmdPtr = clientData;
    Tcl_Obj *listObjPtr;
    int allMatch, endMatch;
    int i;
    
    tablePtr = blt_table_get_row_tag_table(cmdPtr->table);
    listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL);
    for (hPtr = Blt_FirstHashEntry(tablePtr, &iter); hPtr != NULL;
         hPtr = Blt_NextHashEntry(&iter)) {
        const char *tag;
        int i;
        int match;
        
        tag = Blt_GetHashKey(tablePtr, hPtr);
        match = (objc == 4);
        for (i = 4; i < objc; i++) {
            const char *pattern;

            pattern = Tcl_GetString(objv[i]);
            if (Tcl_StringMatch(tag, pattern)) {
                match = TRUE;
                break;                  /* Found match. */
            }
        }
        if (match) {
            Tcl_Obj *objPtr;

            objPtr = Tcl_NewStringObj(tag, -1);
            Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
        }
    }
    allMatch = endMatch = (objc == 4);
    for (i = 4; i < objc; i++) {
        const char *pattern;

        pattern = Tcl_GetString(objv[i]);
        if (Tcl_StringMatch("all", pattern)) {
           allMatch = TRUE;
        }
        if (Tcl_StringMatch("end", pattern)) {
           endMatch = TRUE;
        }
    }
    if (allMatch) {
        Tcl_Obj *objPtr;

        objPtr = Tcl_NewStringObj("all", 3);
        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
    }
    if (endMatch) {
        Tcl_Obj *objPtr;

        objPtr = Tcl_NewStringObj("end", 3);
        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
    }
    Tcl_SetObjResult(interp, listObjPtr);
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * RowTagSetOp --
 *
 *      Adds one or more tags for a given row.  
 *
 *      tableName row tag set rowName ?tagName ...?
 *
 *---------------------------------------------------------------------------
 */
static int
RowTagSetOp(ClientData clientData, Tcl_Interp *interp, int objc,
            Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    BLT_TABLE table;
    BLT_TABLE_ITERATOR ri;
    int i;

    table = cmdPtr->table;
    if (blt_table_iterate_rows(interp, table, objv[4], &ri) != TCL_OK) {
        return TCL_ERROR;
    }
    for (i = 5; i < objc; i++) {
        const char *tag;
        BLT_TABLE_ROW row;

        tag = Tcl_GetString(objv[i]);
        for (row = blt_table_first_tagged_row(&ri); row != NULL; 
             row = blt_table_next_tagged_row(&ri)) {
            if (blt_table_set_row_tag(interp, table, row, tag) != TCL_OK) {
                return TCL_ERROR;
            }
        }    
    }
    return TCL_OK;
}


/*
 *---------------------------------------------------------------------------
 *
 * RowTagUnsetOp --
 *
 *      Removes one or more tags from a given row. 
 *
 *      tableName row tag unset rowName ?tagName ...?
 *
 *---------------------------------------------------------------------------
 */
static int
RowTagUnsetOp(ClientData clientData, Tcl_Interp *interp, int objc,
              Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    BLT_TABLE table;
    BLT_TABLE_ITERATOR ri;
    int i;

    table = cmdPtr->table;
    if (blt_table_iterate_rows(interp, table, objv[4], &ri) != TCL_OK) {
        return TCL_ERROR;
    }
    for (i = 5; i < objc; i++) {
        const char *tag;
        BLT_TABLE_ROW row;
        
        tag = Tcl_GetString(objv[i]);
        for (row = blt_table_first_tagged_row(&ri); row != NULL; 
             row = blt_table_next_tagged_row(&ri)) {
            if (blt_table_unset_row_tag(table, row, tag)!=TCL_OK) {
                return TCL_ERROR;
            }
        }
    }    
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * RowTagOp --
 *
 *      This procedure is invoked to process tag operations.
 *
 * Results:
 *      A standard TCL result.
 *
 * Side Effects:
 *      See the user documentation.
 *
 *---------------------------------------------------------------------------
 */
static Blt_OpSpec rowTagOps[] =
{
    {"add",     1, RowTagAddOp,     5, 0, "tagName ?rowName ...?",},
    {"delete",  1, RowTagDeleteOp,  5, 0, "tagName ?rowName ...?",},
    {"exists",  1, RowTagExistsOp,  5, 6, "tagName ?rowName?",},
    {"forget",  1, RowTagForgetOp,  4, 0, "?tagName ...?",},
    {"get",     1, RowTagGetOp,     5, 0, "rowName ?pattern ...?",},
    {"indices", 1, RowTagIndicesOp, 4, 0, "?tagName ...?",},
    {"labels",  1, RowTagLabelsOp,  4, 0, "?tagName ...?",},
    {"names",   1, RowTagNamesOp,   4, 0, "?pattern ...?",},
    {"range",   1, RowTagRangeOp,   6, 0, "fromRow toRow ?tagName ...?",},
    {"set",     1, RowTagSetOp,     5, 0, "rowName ?tagName ...?",},
    {"unset",   1, RowTagUnsetOp,   5, 0, "rowName ?tagName ...?",},
};

static int numRowTagOps = sizeof(rowTagOps) / sizeof(Blt_OpSpec);

static int 
RowTagOp(ClientData clientData, Tcl_Interp *interp, int objc,
         Tcl_Obj *const *objv)
{
    Tcl_ObjCmdProc *proc;
    int result;

    proc = Blt_GetOpFromObj(interp, numRowTagOps, rowTagOps, BLT_OP_ARG3, 
                objc, objv, 0);
    if (proc == NULL) {
        return TCL_ERROR;
    }
    result = (*proc)(clientData, interp, objc, objv);
    return result;
}
/*
 *---------------------------------------------------------------------------
 *
 * RowUnsetOp --
 *
 *      Unsets one or more rows of values.  One or more rows may be unset
 *      (using tags or multiple arguments).
 * 
 * Results:
 *      A standard TCL result. If the tag or row index is invalid, TCL_ERROR
 *      is returned and an error message is left in the interpreter result.
 *      
 *      tableName row unset rowName ?indices ...?
 *
 *---------------------------------------------------------------------------
 */
static int
RowUnsetOp(ClientData clientData, Tcl_Interp *interp, int objc,
           Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    BLT_TABLE table;
    BLT_TABLE_ITERATOR ri, ci;
    BLT_TABLE_COLUMN col;
    int result;

    table = cmdPtr->table;
    if (blt_table_iterate_rows(interp, table, objv[3], &ri) != TCL_OK) {
        return TCL_ERROR;
    }
    if (blt_table_iterate_columns_objv(interp, table, objc - 4, objv + 4 , &ci) 
        != TCL_OK) {
        return TCL_ERROR;
    }
    result = TCL_ERROR;
    for (col = blt_table_first_tagged_column(&ci); col != NULL; 
         col = blt_table_next_tagged_column(&ci)) {
        BLT_TABLE_ROW row;

        for (row = blt_table_first_tagged_row(&ri); row != NULL;
             row = blt_table_next_tagged_row(&ri)) {
            if (blt_table_unset_value(table, row, col) != TCL_OK) {
                goto error;
            }
        }
    }
    result = TCL_OK;
 error:
    blt_table_free_iterator_objv(&ci);
    return result;
}

/*
 *---------------------------------------------------------------------------
 *
 * RowValuesOp --
 *
 *      Retrieves a row of values.  The row argument can be either a tag,
 *      label, or row index.  If it is a tag, it must refer to exactly one
 *      row.
 * 
 * Results:
 *      A standard TCL result.  If successful, a list of values is returned in
 *      the interpreter result.  If the row index is invalid, TCL_ERROR is
 *      returned and an error message is left in the interpreter result.
 *      
 *      tableName row values rowName ?valueList?
 *
 *---------------------------------------------------------------------------
 */
static int
RowValuesOp(ClientData clientData, Tcl_Interp *interp, int objc,
            Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    BLT_TABLE table;
    BLT_TABLE_ROW row;

    table = cmdPtr->table;
    row = blt_table_get_row(interp, cmdPtr->table, objv[3]);
    if (row == NULL) {
        return TCL_ERROR;
    }
    if (objc == 4) {
        BLT_TABLE_COLUMN col;
        Tcl_Obj *listObjPtr;

        listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL);
        for (col = blt_table_first_column(cmdPtr->table); col != NULL;
             col = blt_table_next_column(col)) {
            Tcl_Obj *objPtr;
            
            objPtr = blt_table_get_obj(cmdPtr->table, row, col);
            if (objPtr == NULL) {
                objPtr = Tcl_NewStringObj(cmdPtr->emptyString, -1);
            }
            Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
        }
        Tcl_SetObjResult(interp, listObjPtr);
    } else {
        Tcl_Obj **elv;
        int elc;
        size_t i;

        if (Tcl_ListObjGetElements(interp, objv[4], &elc, &elv) != TCL_OK) {
            return TCL_ERROR;
        }
        if (elc > blt_table_num_columns(table)) {
            size_t n;

            n = elc - blt_table_num_columns(table);
            if (blt_table_extend_columns(interp, table, n, NULL) != TCL_OK) {
                return TCL_ERROR;
            }
        }
        for (i = 0; i < elc; i++) {
            BLT_TABLE_COLUMN col;

            col = blt_table_column(table, i);
            if (blt_table_set_obj(interp, table, row, col, elv[i]) != TCL_OK) {
                return TCL_ERROR;
            }
        }
    }
    return TCL_OK;
}


/*
 *---------------------------------------------------------------------------
 *
 * RowOp --
 *
 *      Parses the given command line and calls one of several row-specific
 *      operations.
 *      
 * Results:
 *      Returns a standard TCL result.  It is the result of operation called.
 *
 *---------------------------------------------------------------------------
 */
static Blt_OpSpec rowOps[] =
{
    {"copy",      2, RowCopyOp,     5, 0, "destRow srcRow ?switches?",},
    {"create",    2, RowCreateOp,   3, 0, "?switches...?",},
    {"delete",    2, RowDeleteOp,   3, 0, "?rowName ...?",},
    {"duplicate", 2, RowDupOp,      3, 0, "?rowName ...?",},
    {"empty",     3, RowEmptyOp,    4, 4, "rowName",},
    {"exists",    3, RowExistsOp,   4, 4, "rowName",},
    {"extend",    3, RowExtendOp,   4, 0, "numRows ?switches?",},
    {"get",       1, RowGetOp,      4, 0, "rowName ?switches?",},
    {"index",     4, RowIndexOp,    4, 4, "rowName",},
    {"indices",   4, RowIndicesOp,  3, 0, "?rowName ...?",},
    {"isheader",  3, RowIsHeaderOp, 4, 4, "rowName",},
    {"isnumeric", 3, RowIsNumericOp,4, 4, "rowName",},
    {"join",      1, RowJoinOp,     4, 0, "srcTableName ?switches?",},
    {"label",     5, RowLabelOp,    4, 0, "rowName ?label?",},
    {"labels",    6, RowLabelsOp,   3, 4, "?labelList?",},
    {"move",      1, RowMoveOp,     6, 0, "destRow firstRow lastRow ?switches?",},
    {"names",     2, RowNamesOp,    3, 0, "?pattern ...?",},
    {"nonempty",  3, RowNonEmptyOp, 4, 4, "rowName",},
    {"reorder",   1, RowReorderOp,  4, 4, "rowList",},
    {"set",       1, RowSetOp,      5, 0, "rowName colName ?value...?",},
    {"tag",       1, RowTagOp,      3, 0, "op args...",},
    {"unset",     1, RowUnsetOp,    4, 0, "rowName ?indices...?",},
    {"values",    1, RowValuesOp,   4, 5, "rowName ?valueList?",},
};

static int numRowOps = sizeof(rowOps) / sizeof(Blt_OpSpec);

static int
RowOp(ClientData clientData, Tcl_Interp *interp, int objc,
      Tcl_Obj *const *objv)
{
    Tcl_ObjCmdProc *proc;
    int result;

    proc = Blt_GetOpFromObj(interp, numRowOps, rowOps, BLT_OP_ARG2, objc, 
        objv, 0);
    if (proc == NULL) {
        return TCL_ERROR;
    }
    result = (*proc)(clientData, interp, objc, objv);
    return result;
}

/*
 *---------------------------------------------------------------------------
 *
 * SetOp --
 *
 *      Sets one or more key-value pairs for tables.  One or more tables
 *      may be set.  If any of the columns (keys) given don't already
 *      exist, the columns will be automatically created.  The same holds
 *      true for rows.  If a row index is beyond the end of the table (tags
 *      are always in range), new rows are allocated.
 * 
 * Results:
 *      A standard TCL result. If the tag or index is invalid, TCL_ERROR is
 *      returned and an error message is left in the interpreter result.
 *      
 *      tableName set ?rowName colName value ...?
 *
 *---------------------------------------------------------------------------
 */
static int
SetOp(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    BLT_TABLE table;
    int i;

    if (((objc - 2) % 3) != 0) {
        Tcl_AppendResult(interp, "wrong # args: should be \"", 
                Tcl_GetString(objv[0]), 
                " set ?rowName colName value ...?\"", (char *)NULL);
        return TCL_ERROR;
    }
    table = cmdPtr->table;
    for (i = 2; i < objc; i += 3) {
        BLT_TABLE_ITERATOR ri, ci;
        BLT_TABLE_COLUMN col;

        if (IterateRowsWithCreate(interp, table, objv[i], &ri) != TCL_OK) {
            return TCL_ERROR;
        }
        if (IterateColumnsWithCreate(interp, table, objv[i + 1], &ci)
            != TCL_OK) {
            return TCL_ERROR;
        }
        for (col = blt_table_first_tagged_column(&ci); col != NULL; 
             col = blt_table_next_tagged_column(&ci)) {
            BLT_TABLE_ROW row;

            for (row = blt_table_first_tagged_row(&ri); row != NULL; 
                 row = blt_table_next_tagged_row(&ri)) {
                if (blt_table_set_obj(interp, table, row, col, objv[i + 2])
                    != TCL_OK) {
                    return TCL_ERROR;
                }
            }
        }           
    }
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * SortOp --
 *  
 *      tableName sort ?switches?
 *
 *---------------------------------------------------------------------------
 */
static int
SortOp(ClientData clientData, Tcl_Interp *interp, int objc,
       Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    BLT_TABLE_ROW *map;
    BLT_TABLE_COLUMN col;
    BLT_TABLE_SORT_ORDER *sp, *order;
    SortSwitches switches;
    int result;
    long numColumns, numRows;

    result = TCL_ERROR;
    order = NULL;
    map = NULL;

    /* Process switches  */
    memset(&switches, 0, sizeof(SortSwitches));
    switches.tsFlags = TABLE_SORT_AUTO;
    rowIterSwitch.clientData = cmdPtr->table;
    columnIterSwitch.clientData = cmdPtr->table;
    blt_table_iterate_all_columns(cmdPtr->table, &switches.ci);
    if (Blt_ParseSwitches(interp, sortSwitches, objc - 2, objv + 2, &switches, 
        BLT_SWITCH_DEFAULTS) < 0) {
        return TCL_ERROR;               
    }
    if (switches.flags & SORT_ALTER) {
        if (switches.flags & SORT_UNIQUE) {
            Tcl_AppendResult(interp,
                 "-alter and -unique switches are incompatible",
                (char *)NULL);
            goto error;
        }
        if (switches.flags & SORT_NONEMPTY) {
            Tcl_AppendResult(interp,
                 "-alter and -empty switches are incompatible",
                (char *)NULL);
            goto error;
        }
        if (switches.ri.numEntries > 0) {
            Tcl_AppendResult(interp,
                 "-alter and -rows switches are incompatible",
                (char *)NULL);
            goto error;
        }
    }
    numColumns = switches.ci.numEntries;
    if (numColumns == 0) {
        goto error;
    }
    sp = order = Blt_AssertCalloc(numColumns, sizeof(BLT_TABLE_SORT_ORDER));
    /* Then add the secondary sorting columns. */
    for (col = blt_table_first_tagged_column(&switches.ci); col != NULL; 
         col = blt_table_next_tagged_column(&switches.ci)) {
        sp->column = col;
        sp++;
    }
    blt_table_sort_init(cmdPtr->table, order, numColumns, switches.tsFlags);
    col = order[0].column;
    /* Create a row map, pruning out empty values if necessary. */
    if (switches.ri.numEntries > 0) {
        long i;
        BLT_TABLE_ROW row;

        i = 0;
        map = Blt_AssertCalloc(switches.ri.numEntries, sizeof(BLT_TABLE_ROW));
        for (row = blt_table_first_tagged_row(&switches.ri); row != NULL; 
             row = blt_table_next_tagged_row(&switches.ri)) {
            if (switches.flags & SORT_NONEMPTY) {
                if (!blt_table_value_exists(cmdPtr->table, row, col)) {
                    continue;
                }
            }
            map[i] = row;
            i++;
        }
        numRows = i;
    } else {
        long i;
        BLT_TABLE_ROW row;

        i = 0;
        map = Blt_AssertCalloc(blt_table_num_rows(cmdPtr->table),
                               sizeof(BLT_TABLE_ROW));
        for (row = blt_table_first_row(cmdPtr->table); row != NULL; 
             row = blt_table_next_row(row)) {
            if (switches.flags & SORT_NONEMPTY) {
                if (!blt_table_value_exists(cmdPtr->table, row, col)) {
                    continue;
                }
            }
            map[i] = row;
            i++;
        }
        numRows = i;
    }
    /* Sort the row map. */
    blt_table_sort_row_map(cmdPtr->table, numRows, map);
    
    if ((switches.freqArrVarObjPtr != NULL) || 
        (switches.freqListVarObjPtr != NULL) || 
        (switches.flags & SORT_BYFREQ)) {
        if (SortFrequencies(interp, cmdPtr, numRows, map, col, &switches) 
            != TCL_OK) {
            goto error;
        }
    } 
    if (switches.flags & SORT_UNIQUE) {
        result = SetUniqueSortedResult(interp, cmdPtr, numRows, map, col, 
                                       &switches);
    } else {
        result = SetSortedResult(interp, cmdPtr, numRows, map, col, &switches);
    }
    if (result != TCL_OK) {
        goto error;
    }
    Blt_Free(order);
    if (switches.flags & SORT_ALTER) {
        /* Make row order permanent. */
        blt_table_set_row_map(cmdPtr->table, map);
    }
    if (result != TCL_OK) {
        goto error;
    }
    blt_table_sort_finish();
    Blt_FreeSwitches(sortSwitches, &switches, 0);
    return TCL_OK;

 error:
    if (map != NULL) {
        Blt_Free(map);
    }
    if (order != NULL) {
        Blt_Free(order);
    }
    blt_table_sort_finish();
    Blt_FreeSwitches(sortSwitches, &switches, 0);
    return result;
}

/*
 *---------------------------------------------------------------------------
 *
 * TraceCellOp --
 *
 *      Creates a trace for this instance.  Traces represent list of keys, a
 *      bitmask of trace flags, and a command prefix to be invoked when a
 *      matching trace event occurs.
 *
 *      The command prefix is parsed and saved in an array of Tcl_Objs. The
 *      qualified name of the instance is saved also.
 *
 * Results:
 *      A standard TCL result.  The name of the new trace is returned in the
 *      interpreter result.  Otherwise, if it failed to parse a switch, then
 *      TCL_ERROR is returned and an error message is left in the interpreter
 *      result.
 *
 *      tableName trace create row column how command
 *
 *---------------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
TraceCellOp(ClientData clientData, Tcl_Interp *interp, int objc,
            Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    BLT_TABLE table;
    BLT_TABLE_TRACE trace;
    TraceInfo *tracePtr;
    const char *rowTag, *colTag, *colName, *rowName;
    int flags;
    BLT_TABLE_ROWCOLUMN_SPEC rowSpec, colSpec;
    BLT_TABLE_ROW row;
    BLT_TABLE_COLUMN col;
    
    table = cmdPtr->table;
    rowSpec = blt_table_row_spec(table, objv[3], &rowName);
    colSpec = blt_table_column_spec(table, objv[4], &colName);
    flags = GetTraceFlags(Tcl_GetString(objv[5]));
    if (flags < 0) {
        Tcl_AppendResult(interp, "unknown flag in \"", Tcl_GetString(objv[5]), 
                "\"", (char *)NULL);
        return TCL_ERROR;
    }
    row = NULL;
    col = NULL;
    colTag = rowTag = NULL;
    switch (rowSpec) {
    case TABLE_SPEC_RANGE:
        Tcl_AppendResult(interp, "can't trace multiple rows \"", rowName,
                         "\": use a tag instead", (char *)NULL);
        return TCL_ERROR;
    case TABLE_SPEC_INDEX:
    case TABLE_SPEC_LABEL:
        row = blt_table_get_row(interp, table, objv[3]);
        break;
    default:
        rowTag = rowName;
    }
    switch (colSpec) {
    case TABLE_SPEC_RANGE:
        Tcl_AppendResult(interp, "can't trace multiple columns \"", colName,
                         "\": use a tag instead", (char *)NULL);
        return TCL_ERROR;
    case TABLE_SPEC_INDEX:
    case TABLE_SPEC_LABEL:
        col = blt_table_get_column(interp, table, objv[4]);
        break;
    default:
        colTag = colName;
    }
    tracePtr = Blt_Malloc(sizeof(TraceInfo));
    if (tracePtr == NULL) {
        Tcl_AppendResult(interp, "can't allocate trace: out of memory", 
                (char *)NULL);
        return TCL_ERROR;
    }
    trace = blt_table_create_trace(table, row, col, rowTag, colTag, flags, 
        TraceProc, TraceDeleteProc, tracePtr);
    if (trace == NULL) {
        Tcl_AppendResult(interp, "can't create individual trace: out of memory",
                (char *)NULL);
        Blt_Free(tracePtr);
        return TCL_ERROR;
    }
    /* Initialize the trace information structure. */
    tracePtr->cmdPtr = cmdPtr;
    tracePtr->trace = trace;
    tracePtr->tablePtr = &cmdPtr->traceTable;
    {
        Tcl_Obj **elv, *objPtr;
        int elc;

        if (Tcl_ListObjGetElements(interp, objv[6], &elc, &elv) != TCL_OK) {
            return TCL_ERROR;
        }
        tracePtr->cmdObjPtr = Tcl_NewListObj(elc, elv);
        objPtr = Tcl_NewStringObj(cmdPtr->hPtr->key.string, -1);
        Tcl_ListObjAppendElement(interp, tracePtr->cmdObjPtr, objPtr);
        Tcl_IncrRefCount(tracePtr->cmdObjPtr);
    }
    {
        int isNew;
        char traceId[200];
        Blt_HashEntry *hPtr;

        do {
            Blt_FmtString(traceId, 200, "trace%d", cmdPtr->nextTraceId);
            cmdPtr->nextTraceId++;
            hPtr = Blt_CreateHashEntry(&cmdPtr->traceTable, traceId, &isNew);
        } while (!isNew);
        tracePtr->hPtr = hPtr;
        Blt_SetHashValue(hPtr, tracePtr);
        Tcl_SetStringObj(Tcl_GetObjResult(interp), traceId, -1);
    }
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * TraceColumnOp --
 *
 *      Creates a trace for this instance.  Traces represent list of keys,
 *      a bitmask of trace flags, and a command prefix to be invoked when a
 *      matching trace event occurs.
 *
 *      The command prefix is parsed and saved in an array of Tcl_Objs. The
 *      qualified name of the instance is saved also.
 *
 * Results:
 *      A standard TCL result.  The name of the new trace is returned in the
 *      interpreter result.  Otherwise, if it failed to parse a switch, then
 *      TCL_ERROR is returned and an error message is left in the interpreter
 *      result.
 *
 *      tableName trace column tag rwx proc 
 *
 *---------------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
TraceColumnOp(ClientData clientData, Tcl_Interp *interp, int objc,
              Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    BLT_TABLE_TRACE trace;
    TraceInfo *tracePtr;
    int flags;
    BLT_TABLE table;
    BLT_TABLE_COLUMN col;
    BLT_TABLE_ROWCOLUMN_SPEC colSpec;
    const char *colTag, *colName;

    colTag = NULL;
    col = NULL;
    table = cmdPtr->table;
    colSpec = blt_table_column_spec(table, objv[3], &colName);
    flags = GetTraceFlags(Tcl_GetString(objv[4]));
    if (flags < 0) {
        Tcl_AppendResult(interp, "unknown flag in \"", Tcl_GetString(objv[4]), 
                "\"", (char *)NULL);
        return TCL_ERROR;
    }
    col = NULL;
    colTag = NULL;
    switch (colSpec) {
    case TABLE_SPEC_RANGE:
        Tcl_AppendResult(interp, "can't trace multiple columns \"", colName,
                         "\": use a tag instead", (char *)NULL);
        return TCL_ERROR;
    case TABLE_SPEC_INDEX:
    case TABLE_SPEC_LABEL:
        col = blt_table_get_column(interp, table, objv[3]);
        break;
    default:
        colTag = colName;
    }
    tracePtr = Blt_AssertMalloc(sizeof(TraceInfo));
    if (tracePtr == NULL) {
        Tcl_AppendResult(interp, "can't allocate trace: out of memory", 
                (char *)NULL);
        return TCL_ERROR;
    }
    trace = blt_table_create_trace(table, NULL, col, NULL, colTag, flags, 
        TraceProc, TraceDeleteProc, tracePtr);
    if (trace == NULL) {
        Tcl_AppendResult(interp, "can't create column trace: out of memory", 
                (char *)NULL);
        return TCL_ERROR;
    }
    /* Initialize the trace information structure. */
    tracePtr->cmdPtr = cmdPtr;
    tracePtr->trace = trace;
    tracePtr->tablePtr = &cmdPtr->traceTable;
    {
        Tcl_Obj **elv, *objPtr;
        int elc;

        if (Tcl_ListObjGetElements(interp, objv[5], &elc, &elv) != TCL_OK) {
            return TCL_ERROR;
        }
        tracePtr->cmdObjPtr = Tcl_NewListObj(elc, elv);;
        objPtr = Tcl_NewStringObj(cmdPtr->hPtr->key.string, -1);
        Tcl_ListObjAppendElement(interp, tracePtr->cmdObjPtr, objPtr);
        Tcl_IncrRefCount(tracePtr->cmdObjPtr);
    }
    {
        char traceId[200];
        int isNew;

        Blt_FmtString(traceId, 200, "trace%d", cmdPtr->nextTraceId);
        cmdPtr->nextTraceId++;
        tracePtr->hPtr = Blt_CreateHashEntry(&cmdPtr->traceTable, traceId, 
                &isNew);
        Blt_SetHashValue(tracePtr->hPtr, tracePtr);
        Tcl_SetStringObj(Tcl_GetObjResult(interp), traceId, -1);
    }
    return TCL_OK;
}


/*
 *---------------------------------------------------------------------------
 *
 * TraceDeleteOp --
 *
 *      Deletes one or more traces.  Can be any type of trace.
 *
 * Results:
 *      A standard TCL result.  If a name given doesn't represent a trace,
 *      then TCL_ERROR is returned and an error message is left in the
 *      interpreter result.
 *
 *      tableName trace delete ?traceName ...?
 *
 *---------------------------------------------------------------------------
 */
static int
TraceDeleteOp(ClientData clientData, Tcl_Interp *interp, int objc,
              Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    int i;

    for (i = 3; i < objc; i++) {
        Blt_HashEntry *hPtr;
        TraceInfo *tracePtr;

        hPtr = Blt_FindHashEntry(&cmdPtr->traceTable, Tcl_GetString(objv[i]));
        if (hPtr == NULL) {
            Tcl_AppendResult(interp, "unknown trace \"", 
                             Tcl_GetString(objv[i]), "\"", (char *)NULL);
            return TCL_ERROR;
        }
        tracePtr = Blt_GetHashValue(hPtr);
        blt_table_delete_trace(cmdPtr->table, tracePtr->trace);
    }
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * TraceInfoOp --
 *
 *      Returns the details for a given trace.  The name of the trace is
 *      passed as an argument.  The details are returned as a list of
 *      key-value pairs: trace name, tag, row index, keys, flags, and the
 *      command prefix.
 *
 * Results:
 *      A standard TCL result.  If the name given doesn't represent a trace,
 *      then TCL_ERROR is returned and an error message is left in the
 *      interpreter result.
 *
 *      tableName trace info traceName
 *
 *---------------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
TraceInfoOp(ClientData clientData, Tcl_Interp *interp, int objc,
            Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    Blt_HashEntry *hPtr;
    TraceInfo *tracePtr;
    Tcl_Obj *listObjPtr;

    hPtr = Blt_FindHashEntry(&cmdPtr->traceTable, Tcl_GetString(objv[3]));
    if (hPtr == NULL) {
        Tcl_AppendResult(interp, "unknown trace \"", Tcl_GetString(objv[3]), 
                "\"", (char *)NULL);
        return TCL_ERROR;
    }
    tracePtr = Blt_GetHashValue(hPtr);
    listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL);
    PrintTraceInfo(interp, tracePtr, listObjPtr);
    Tcl_SetObjResult(interp, listObjPtr);
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * TraceNamesOp --
 *
 *      Returns the names of all the traces in use by this instance.
 *      Traces created by other instances or object clients are not
 *      reported.
 *
 * Results:
 *      Always TCL_OK.  A list of trace names is left in the interpreter
 *      result.
 *
 *      tableName trace names ?pattern ...?
 *
 *---------------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
TraceNamesOp(ClientData clientData, Tcl_Interp *interp, int objc,
             Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    Blt_HashEntry *hPtr;
    Blt_HashSearch iter;
    Tcl_Obj *listObjPtr;

    listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL);
    for (hPtr = Blt_FirstHashEntry(&cmdPtr->traceTable, &iter);
         hPtr != NULL; hPtr = Blt_NextHashEntry(&iter)) {
        const char *name;
        int i, match;
        
        name = Blt_GetHashKey(&cmdPtr->watchTable, hPtr);
        match = (objc == 3);
        for (i = 3; i < objc; i++) {
            const char *pattern;

            pattern = Tcl_GetString(objv[i]);
            if (Tcl_StringMatch(name, pattern)) {
                match = TRUE;
                break;                  /* Found match. */
            }
        }
        if (match) {
            Tcl_Obj *objPtr;

            objPtr = Tcl_NewStringObj(name, -1);
            Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
        }
    }
    Tcl_SetObjResult(interp, listObjPtr);
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * TraceRowOp --
 *
 *      Creates a trace for this instance. Traces represent list of keys, a
 *      bitmask of trace flags, and a command prefix to be invoked when a
 *      matching trace event occurs.
 *
 *      The command prefix is parsed and saved in an array of Tcl_Objs. The
 *      qualified name of the instance is saved also.
 *
 * Results:
 *      A standard TCL result. The name of the new trace is returned in the
 *      interpreter result. Otherwise, if it failed to parse a switch, then
 *      TCL_ERROR is returned and an error message is left in the
 *      interpreter result.
 *
 *      tableName trace row tagName rwx cmdString 
 *
 *---------------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
TraceRowOp(ClientData clientData, Tcl_Interp *interp, int objc,
           Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    BLT_TABLE table;
    BLT_TABLE_TRACE trace;
    TraceInfo *tracePtr;
    int flags;
    BLT_TABLE_ROW row;
    BLT_TABLE_ROWCOLUMN_SPEC rowSpec;
    const char *rowTag, *rowName;

    table = cmdPtr->table;
    rowSpec = blt_table_row_spec(table, objv[3], &rowName);
    flags = GetTraceFlags(Tcl_GetString(objv[4]));
    if (flags < 0) {
        Tcl_AppendResult(interp, "unknown flag in \"", Tcl_GetString(objv[4]), 
                "\"", (char *)NULL);
        return TCL_ERROR;
    }
    row = NULL;
    rowTag = NULL;
    switch (rowSpec) {
    case TABLE_SPEC_RANGE:
        Tcl_AppendResult(interp, "can't trace row ranges \"",
                Tcl_GetString(objv[3]), "\": use a tag instead", 
                (char *)NULL);
        return TCL_ERROR;
    case TABLE_SPEC_INDEX:
    case TABLE_SPEC_LABEL:
        row = blt_table_get_row(interp, table, objv[3]);
        break;
    default:
        rowTag = rowName;
    }
    tracePtr = Blt_Malloc(sizeof(TraceInfo));
    if (tracePtr == NULL) {
        Tcl_AppendResult(interp, "can't allocate trace: out of memory", 
                (char *)NULL);
        return TCL_ERROR;
    }
    trace = blt_table_create_trace(table, row, NULL, rowTag, NULL, flags, 
        TraceProc, TraceDeleteProc, tracePtr);
    if (trace == NULL) {
        Tcl_AppendResult(interp, "can't create row trace: out of memory", 
                (char *)NULL);
        Blt_Free(tracePtr);
        return TCL_ERROR;
    }
    /* Initialize the trace information structure. */
    tracePtr->cmdPtr = cmdPtr;
    tracePtr->trace = trace;
    tracePtr->tablePtr = &cmdPtr->traceTable;
    {
        Tcl_Obj **elv, *objPtr;
        int elc;

        if (Tcl_ListObjGetElements(interp, objv[5], &elc, &elv) != TCL_OK) {
            return TCL_ERROR;
        }
        tracePtr->cmdObjPtr = Tcl_NewListObj(elc, elv);
        objPtr = Tcl_NewStringObj(cmdPtr->hPtr->key.string, -1);
        Tcl_ListObjAppendElement(interp, tracePtr->cmdObjPtr, objPtr);
        Tcl_IncrRefCount(tracePtr->cmdObjPtr);
    }
    {
        char traceId[200];
        int isNew;

        Blt_FmtString(traceId, 200, "trace%d", cmdPtr->nextTraceId);
        cmdPtr->nextTraceId++;
        tracePtr->hPtr = Blt_CreateHashEntry(&cmdPtr->traceTable, traceId, 
                &isNew);
        Blt_SetHashValue(tracePtr->hPtr, tracePtr);
        Tcl_SetStringObj(Tcl_GetObjResult(interp), traceId, -1);
    }
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * TraceOp --
 *
 *      This procedure is invoked to process trace operations.
 *
 * Results:
 *      A standard TCL result.
 *
 * Side Effects:
 *      See the user documentation.
 *
 *---------------------------------------------------------------------------
 */
static Blt_OpSpec traceOps[] =
{
    {"cell",   2, TraceCellOp,   7, 7, "rowName colName how command",},
    {"column", 2, TraceColumnOp, 6, 6, "colName how command",},
    {"delete", 1, TraceDeleteOp, 3, 0, "?traceName ...?",},
    {"info",   1, TraceInfoOp,   4, 4, "traceName",},
    {"names",  1, TraceNamesOp,  3, 0, "?pattern ...?",},
    {"row",    1, TraceRowOp,    6, 6, "rowName how command",},
};

static int numTraceOps = sizeof(traceOps) / sizeof(Blt_OpSpec);

static int
TraceOp(ClientData clientData, Tcl_Interp *interp, int objc,
        Tcl_Obj *const *objv)
{
    Tcl_ObjCmdProc *proc;
    int result;

    proc = Blt_GetOpFromObj(interp, numTraceOps, traceOps, BLT_OP_ARG2, objc, 
        objv, 0);
    if (proc == NULL) {
        return TCL_ERROR;
    }
    result = (*proc)(clientData, interp, objc, objv);
    return result;
}

/*
 *---------------------------------------------------------------------------
 *
 * UnsetOp --
 *
 *      Unsets one or more values.  One or more tables may be unset (using
 *      tags).  It's not an error if one of the key names (columns) doesn't
 *      exist.  The same holds true for rows.  If a row index is beyond the
 *      end of the table (tags are always in range), it is also ignored.
 * 
 * Results:
 *      A standard TCL result. If the tag or index is invalid, TCL_ERROR is
 *      returned and an error message is left in the interpreter result.
 *      
 *      tableName unset ?rowName colName ...?
 *
 *---------------------------------------------------------------------------
 */
static int
UnsetOp(ClientData clientData, Tcl_Interp *interp, int objc,
        Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    BLT_TABLE table;
    int i;

    if ((objc - 2) & 1) {
        Tcl_AppendResult(interp, "wrong # args: should be \"", 
                Tcl_GetString(objv[0]), " unset ?rowName colName ...?\"", 
                (char *)NULL);
        return TCL_ERROR;
    }
    table = cmdPtr->table;
    for (i = 2; i < objc; i += 2) {
        BLT_TABLE_ITERATOR ri, ci;
        BLT_TABLE_COLUMN col;

        if (blt_table_iterate_rows(NULL, table, objv[i], &ri) != TCL_OK) {
            return TCL_OK;
        }
        if (blt_table_iterate_columns(NULL, table, objv[i+1], &ci) != TCL_OK) {
            return TCL_OK;
        }
        for (col = blt_table_first_tagged_column(&ci); col != NULL; 
             col = blt_table_next_tagged_column(&ci)) {
            BLT_TABLE_ROW row;

            for (row = blt_table_first_tagged_row(&ri); row != NULL; 
                 row = blt_table_next_tagged_row(&ri)) {

                if (blt_table_unset_value(table, row, col) != TCL_OK) {
                    return TCL_ERROR;
                }
            }
        }           
    }
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * WatchColumnOp --
 *
 *      Creates a notifier for this instance.  Notifiers represent a bitmask
 *      of events and a command prefix to be invoked when a matching event
 *      occurs.
 *
 *      The command prefix is parsed and saved in an array of Tcl_Objs. Extra
 *      slots are allocated for the
 *
 * Results:
 *      A standard TCL result.  The name of the new notifier is returned in
 *      the interpreter result.  Otherwise, if it failed to parse a switch,
 *      then TCL_ERROR is returned and an error message is left in the
 *      interpreter result.
 *
 *      tableName column watch colName ?flags? command arg
 *
 *---------------------------------------------------------------------------
 */
static int
WatchColumnOp(ClientData clientData, Tcl_Interp *interp, int objc,
              Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    BLT_TABLE table;
    BLT_TABLE_COLUMN col;
    BLT_TABLE_ROWCOLUMN_SPEC spec;
    WatchInfo *watchPtr;
    WatchSwitches switches;
    const char *tag, *string;
    int count, i;

    table = cmdPtr->table;
    spec = blt_table_column_spec(table, objv[3], &string);
    col = NULL;
    tag = NULL;
    if (spec == TABLE_SPEC_TAG) {
        tag = string;
    } else {
        col = blt_table_get_column(interp, table, objv[3]);
        if (col == NULL) {
            return TCL_ERROR;
        }
    }
    count = 0;
    for (i = 4; i < objc; i++) {
        const char *string;

        string = Tcl_GetString(objv[i]);
        if (string[0] != '-') {
            break;
        }
        count++;
    }
    switches.flags = 0;
    /* Process switches  */
    if (Blt_ParseSwitches(interp, watchSwitches, count, objv + 4, &switches, 
        0) < 0) {
        return TCL_ERROR;
    }
    watchPtr = Blt_AssertMalloc(sizeof(WatchInfo));
    watchPtr->cmdPtr = cmdPtr;
    if (tag == NULL) {
        watchPtr->notifier = blt_table_create_column_notifier(interp, 
                cmdPtr->table, col, switches.flags, NotifyProc, 
                NotifierDeleteProc, watchPtr);
    } else {
        watchPtr->notifier = blt_table_create_column_tag_notifier(interp, 
                cmdPtr->table, tag, switches.flags, NotifyProc, 
                NotifierDeleteProc, watchPtr);
    }   
    /* Stash away the command in structure and pass that to the notifier. */
    watchPtr->cmdObjPtr = Tcl_NewListObj(objc - i, objv + i);
    Tcl_IncrRefCount(watchPtr->cmdObjPtr);
    if (switches.flags == 0) {
        switches.flags = TABLE_NOTIFY_ALL_EVENTS;
    }
    {
        char name[200];
        Blt_HashEntry *hPtr;
        int isNew;

        Blt_FmtString(name, 200, "watch%d", cmdPtr->nextWatch++);
        hPtr = Blt_CreateHashEntry(&cmdPtr->watchTable, name, &isNew);
        assert(isNew);
        Blt_SetHashValue(hPtr, watchPtr);
        Tcl_SetStringObj(Tcl_GetObjResult(interp), name, -1);
    }
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * WatchDeleteOp --
 *
 *      Deletes one or more notifiers.  
 *
 * Results:
 *      A standard TCL result.  If a name given doesn't represent a notifier,
 *      then TCL_ERROR is returned and an error message is left in the
 *      interpreter result.
 *
 *      tableName watch delete ?watchName ...?
 *
 *---------------------------------------------------------------------------
 */
static int
WatchDeleteOp(ClientData clientData, Tcl_Interp *interp, int objc, 
               Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    int i;

    for (i = 3; i < objc; i++) {
        Blt_HashEntry *hPtr;
        WatchInfo *watchPtr;

        hPtr = Blt_FindHashEntry(&cmdPtr->watchTable, Tcl_GetString(objv[i]));
        if (hPtr == NULL) {
            Tcl_AppendResult(interp, "unknown watch id \"", 
                Tcl_GetString(objv[i]), "\"", (char *)NULL);
            return TCL_ERROR;
        }
        watchPtr = Blt_GetHashValue(hPtr);
        Blt_DeleteHashEntry(&cmdPtr->watchTable, hPtr);
        FreeWatchInfo(watchPtr);
    }
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * WatchInfoOp --
 *
 *      Returns the details for a given notifier.  The string id of the
 *      notifier is passed as an argument.
 *
 * Results:
 *      A standard TCL result.  If the name given doesn't represent a
 *      notifier, then TCL_ERROR is returned and an error message is left
 *      in the interpreter result.  Otherwise the details of the notifier
 *      handler are returned as a list of three elements: notifier id,
 *      flags, and command.
 *
 *---------------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
WatchInfoOp(ClientData clientData, Tcl_Interp *interp, int objc,
             Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    Blt_HashEntry *hPtr;
    WatchInfo *watchPtr;
    Tcl_Obj *listObjPtr, *subListObjPtr, *objPtr;
    struct _BLT_TABLE_NOTIFIER *notifierPtr;

    hPtr = Blt_FindHashEntry(&cmdPtr->watchTable, Tcl_GetString(objv[3]));
    if (hPtr == NULL) {
        Tcl_AppendResult(interp, "unknown watch id \"", 
                Tcl_GetString(objv[3]), "\"", (char *)NULL);
        return TCL_ERROR;
    }
    watchPtr = Blt_GetHashValue(hPtr);
    notifierPtr = watchPtr->notifier;

    listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL);
    Tcl_ListObjAppendElement(interp, listObjPtr, objv[3]); /* Copy watch Id */

    subListObjPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL);
    if (notifierPtr->flags & TABLE_NOTIFY_CREATE) {
        objPtr = Tcl_NewStringObj("-create", -1);
        Tcl_ListObjAppendElement(interp, subListObjPtr, objPtr);
    }
    if (notifierPtr->flags & TABLE_NOTIFY_DELETE) {
        objPtr = Tcl_NewStringObj("-delete", -1);
        Tcl_ListObjAppendElement(interp, subListObjPtr, objPtr);
    }
    if (notifierPtr->flags & TABLE_NOTIFY_WHENIDLE) {
        objPtr = Tcl_NewStringObj("-whenidle", -1);
        Tcl_ListObjAppendElement(interp, subListObjPtr, objPtr);
    }
    if (notifierPtr->flags & TABLE_NOTIFY_RELABEL) {
        objPtr = Tcl_NewStringObj("-relabel", -1);
        Tcl_ListObjAppendElement(interp, subListObjPtr, objPtr);
    }
    Tcl_ListObjAppendElement(interp, listObjPtr, subListObjPtr);
    if (notifierPtr->flags & TABLE_NOTIFY_ROW) {
        Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewStringObj("row",3));
        if (notifierPtr->tag != NULL) {
            Tcl_ListObjAppendElement(interp, listObjPtr, 
                Tcl_NewStringObj(notifierPtr->tag, -1));
        } else {
            Tcl_ListObjAppendElement(interp, listObjPtr, 
               GetRowIndexObj(watchPtr->cmdPtr->table, notifierPtr->row));
        }
    } else {
        Tcl_ListObjAppendElement(interp, listObjPtr, 
                Tcl_NewStringObj("column", 6));
        if (notifierPtr->tag != NULL) {
            Tcl_ListObjAppendElement(interp, listObjPtr, 
                Tcl_NewStringObj(notifierPtr->tag, -1));
        } else {
            Tcl_ListObjAppendElement(interp, listObjPtr, 
                GetColumnIndexObj(watchPtr->cmdPtr->table, notifierPtr->column));
        }
    }
    Tcl_ListObjAppendElement(interp, listObjPtr, watchPtr->cmdObjPtr);
    Tcl_SetObjResult(interp, listObjPtr);
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * WatchNamesOp --
 *
 *      Returns the names of all the notifiers in use by this instance.
 *      Notifiers issues by other instances or object clients are not
 *      reported.
 *
 * Results:
 *      Always TCL_OK.  A list of notifier names is left in the interpreter
 *      result.
 *
 *      tableName watch names ?pattern ...?
 *
 *---------------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
WatchNamesOp(ClientData clientData, Tcl_Interp *interp, int objc,
              Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    Blt_HashEntry *hPtr;
    Blt_HashSearch iter;
    Tcl_Obj *listObjPtr;

    listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL);
    for (hPtr = Blt_FirstHashEntry(&cmdPtr->watchTable, &iter); hPtr != NULL;
         hPtr = Blt_NextHashEntry(&iter)) {
        const char *name;
        int i, match;
        
        name = Blt_GetHashKey(&cmdPtr->watchTable, hPtr);
        match = (objc == 3);
        for (i = 3; i < objc; i++) {
            const char *pattern;

            pattern = Tcl_GetString(objv[i]);
            if (Tcl_StringMatch(name, pattern)) {
                match = TRUE;
                break;                  /* Found match. */
            }
        }
        if (match) {
            Tcl_Obj *objPtr;

            objPtr = Tcl_NewStringObj(name, -1);
            Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
        }
    }
    Tcl_SetObjResult(interp, listObjPtr);
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * WatchRowOp --
 *
 *      Creates a notifier for this instance.  Notifiers represent a
 *      bitmask of events and a command prefix to be invoked when a
 *      matching event occurs.
 *
 *      The command prefix is parsed and saved in an array of
 *      Tcl_Objs. Extra slots are allocated for the
 *
 * Results:
 *      A standard TCL result.  The name of the new notifier is returned in
 *      the interpreter result.  Otherwise, if it failed to parse a switch,
 *      then TCL_ERROR is returned and an error message is left in the
 *      interpreter result.
 *
 *      tableName row watch rowName ?flags? command arg
 *
 *---------------------------------------------------------------------------
 */
static int
WatchRowOp(ClientData clientData, Tcl_Interp *interp, int objc,
           Tcl_Obj *const *objv)
{
    Cmd *cmdPtr = clientData;
    BLT_TABLE table;
    WatchInfo *watchPtr;
    WatchSwitches switches;
    const char *tag, *string;
    int count;
    int i;
    BLT_TABLE_ROW row;
    BLT_TABLE_ROWCOLUMN_SPEC spec;

    table = cmdPtr->table;
    spec = blt_table_row_spec(table, objv[3], &string);
    row = NULL;
    tag = NULL;
    if (spec == TABLE_SPEC_TAG) {
        tag = string;
    } else {
        row = blt_table_get_row(interp, table, objv[3]);
        if (row == NULL) {
            return TCL_ERROR;
        }
    }
    count = 0;
    for (i = 4; i < objc; i++) {
        const char *string;

        string = Tcl_GetString(objv[i]);
        if (string[0] != '-') {
            break;
        }
        count++;
    }
    switches.flags = 0;
    /* Process switches  */
    if (Blt_ParseSwitches(interp, watchSwitches, count, objv + 4, 
             &switches, 0) < 0) {
        return TCL_ERROR;
    }
    watchPtr = Blt_AssertMalloc(sizeof(WatchInfo));
    watchPtr->cmdPtr = cmdPtr;
    if (tag == NULL) {
        watchPtr->notifier = blt_table_create_row_notifier(interp, 
                cmdPtr->table, row, switches.flags, NotifyProc, 
                NotifierDeleteProc, watchPtr);
    } else {
        watchPtr->notifier = blt_table_create_row_tag_notifier(interp, 
                cmdPtr->table, tag, switches.flags, NotifyProc, 
                NotifierDeleteProc, watchPtr);
    }   
    /* Stash away the command in structure and pass that to the notifier. */
    watchPtr->cmdObjPtr = Tcl_NewListObj(objc - i, objv + i);
    Tcl_IncrRefCount(watchPtr->cmdObjPtr);
    if (switches.flags == 0) {
        switches.flags = TABLE_NOTIFY_ALL_EVENTS;
    }
    {
        char name[200];
        Blt_HashEntry *hPtr;
        int isNew;

        Blt_FmtString(name, 200, "watch%d", cmdPtr->nextWatch++);
        hPtr = Blt_CreateHashEntry(&cmdPtr->watchTable, name, &isNew);
        assert(isNew);
        Blt_SetHashValue(hPtr, watchPtr);
        Tcl_SetStringObj(Tcl_GetObjResult(interp), name, -1);
    }
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * WatchOp --
 *
 *      Parses the given command line and calls one of several notifier
 *      specific operations.
 *      
 * Results:
 *      Returns a standard TCL result.  It is the result of operation
 *      called.
 *
 *---------------------------------------------------------------------------
 */
static Blt_OpSpec watchOps[] =
{
    {"column", 1, WatchColumnOp, 5, 0, "column ?flags? command",},
    {"delete", 1, WatchDeleteOp, 3, 0, "?watchName ...?",},
    {"info",   1, WatchInfoOp,   4, 4, "watchName",},
    {"names",  1, WatchNamesOp,  3, 0, "?pattern ...?",},
    {"row",    1, WatchRowOp,    5, 0, "row ?flags? command",},
};

static int numWatchOps = sizeof(watchOps) / sizeof(Blt_OpSpec);

static int
WatchOp(ClientData clientData, Tcl_Interp *interp, int objc,
         Tcl_Obj *const *objv)
{
    Tcl_ObjCmdProc *proc;
    int result;

    proc = Blt_GetOpFromObj(interp, numWatchOps, watchOps, BLT_OP_ARG2, objc, 
        objv, 0);
    if (proc == NULL) {
        return TCL_ERROR;
    }
    result = (*proc)(clientData, interp, objc, objv);
    return result;
}

static Blt_OpSpec tableOps[] =
{
    {"add",        2, AddOp,        3, 0, "tableName ?switches?",},
    {"append",     2, AppendOp,     5, 0, "rowName colName ?value ...?",},
    {"attach",     2, AttachOp,     2, 3, "tableName",},
    {"clear",      2, ClearOp,      2, 2, "",},
    {"column",     3, ColumnOp,     3, 0, "op args...",},
    {"copy",       3, CopyOp,       3, 3, "tableName",},
    {"dir",        2, DirOp,        3, 0, "path ?switches?",},
    {"dump",       3, DumpOp,       2, 0, "?switches?",},
    {"duplicate",  3, DuplicateOp,  2, 3, "?tableName?",},
    {"emptyvalue", 2, EmptyValueOp, 2, 3, "?newValue?",},
    {"exists",     3, ExistsOp,     4, 4, "rowName colName",},
    {"export",     3, ExportOp,     2, 0, "formatName args...",},
    {"find",       1, FindOp,       3, 0, "exprString ?switches?",},
    {"get",        1, GetOp,        4, 5, "rowName colName ?defValue?",},
    {"import",     1, ImportOp,     2, 0, "formatName args...",},
    {"keys",       1, KeysOp,       2, 0, "?colName ...?",},
    {"lappend",    2, LappendOp,    5, 0, "rowName colName ?value ...?",},
    {"limits",     2, MinMaxOp,     2, 3, "?colName?",},
    {"lookup",     2, LookupOp,     2, 0, "?value...?",},
    {"maximum",    2, MinMaxOp,     2, 3, "?colName?",},
    {"minimum",    2, MinMaxOp,     2, 3, "?colName?",},
    {"numcolumns", 4, NumColumnsOp, 2, 3, "?numColumns?",},
    {"numrows",    4, NumRowsOp,    2, 3, "?numRows?",},
    {"pack",       1, PackOp,       2, 2, "",},
    {"reset",      4, ResetOp,      2, 2, "",},
    {"restore",    4, RestoreOp,    2, 0, "?switches?",},
    {"row",        2, RowOp,        3, 0, "op args...",},
    {"set",        2, SetOp,        3, 0, "?rowName colName value ...?",},
    {"sort",       2, SortOp,       3, 0, "?flags ...?",},
    {"trace",      2, TraceOp,      2, 0, "op args...",},
    {"Unset",      1, UnsetOp,      4, 0, "?rowName colName ...?",},
    {"watch",      1, WatchOp,      2, 0, "op args...",},
#ifdef notplanned
    {"-apply",     1, ApplyOp,      3, 0, "first last ?switches?",},
#endif
};

static int numTableOps = sizeof(tableOps) / sizeof(Blt_OpSpec);

/*
 *---------------------------------------------------------------------------
 *
 * TableInstObjCmd --
 *
 *      This procedure is invoked to process commands on behalf of * the
 *      instance of the table-object.
 *
 * Results:
 *      A standard TCL result.
 *
 * Side Effects:
 *      See the user documentation.
 *
 *---------------------------------------------------------------------------
 */
static int
TableInstObjCmd(
    ClientData clientData,              /* Pointer to the datatable command
                                         * structure. */
    Tcl_Interp *interp,                 /* Interpreter to report errors. */
    int objc,                           /* # of arguments. */
    Tcl_Obj *const *objv)               /* Vector of argument strings. */
{
    Cmd *cmdPtr = clientData;
    Tcl_ObjCmdProc *proc;
    int result;

    proc = Blt_GetOpFromObj(interp, numTableOps, tableOps, BLT_OP_ARG1, objc, 
        objv, 0);
    if (proc == NULL) {
        return TCL_ERROR;
    }
    Tcl_Preserve(cmdPtr);
    result = (*proc) (clientData, interp, objc, objv);
    Tcl_Release(cmdPtr);
    return result;
}

/*
 *---------------------------------------------------------------------------
 *
 * TableInstDeleteProc --
 *
 *      Deletes the command associated with the table.  This is called only
 *      when the command associated with the table is destroyed.
 *
 * Results:
 *      None.
 *
 * Side Effects:
 *      The table object is released and bookkeeping data for traces and
 *      notifiers are freed.
 *
 *---------------------------------------------------------------------------
 */
static void
TableInstDeleteProc(ClientData clientData)
{
    Blt_HashEntry *hPtr;
    Blt_HashSearch iter;
    Cmd *cmdPtr = clientData;

    for (hPtr = Blt_FirstHashEntry(&cmdPtr->traceTable, &iter); hPtr != NULL;
         hPtr = Blt_NextHashEntry(&iter)) {
        TraceInfo *tracePtr;

        tracePtr = Blt_GetHashValue(hPtr);
        blt_table_delete_trace(cmdPtr->table, tracePtr->trace);
    }
    Blt_DeleteHashTable(&cmdPtr->traceTable);
    for (hPtr = Blt_FirstHashEntry(&cmdPtr->watchTable, &iter); hPtr != NULL;
         hPtr = Blt_NextHashEntry(&iter)) {
        WatchInfo *watchPtr;

        watchPtr = Blt_GetHashValue(hPtr);
        FreeWatchInfo(watchPtr);
    }
    if (cmdPtr->emptyString != NULL) {
        Blt_Free(cmdPtr->emptyString);
    }
    Blt_DeleteHashTable(&cmdPtr->watchTable);
    if (cmdPtr->hPtr != NULL) {
        Blt_DeleteHashEntry(cmdPtr->tablePtr, cmdPtr->hPtr);
    }
    blt_table_close(cmdPtr->table);
    Blt_Free(cmdPtr);
}

/*
 *---------------------------------------------------------------------------
 *
 * TableCreateOp --
 *
 *      Creates a new instance of a table object.  
 *
 *      This routine insures that instance and object names are the same.
 *      For example, you can't create an instance with the name of an
 *      object that already exists.  And because each instance has a TCL
 *      command associated with it (used to access the object), we
 *      additionally check more that it's not an existing TCL command.
 *
 *      Instance names are namespace-qualified.  If the given name doesn't
 *      have a namespace qualifier, that instance will be created in the
 *      current namespace (not the global namespace).
 *      
 * Results:
 *      A standard TCL result.  If the instance is successfully created,
 *      the namespace-qualified name of the instance is returned. If not,
 *      then TCL_ERROR is returned and an error message is left in the
 *      interpreter result.
 *
 *      blt::datatable create ?switches?
 * 
 *---------------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
TableCreateOp(ClientData clientData, Tcl_Interp *interp, int objc, 
              Tcl_Obj *const *objv)
{
    const char *instName;
    Tcl_DString ds;
    BLT_TABLE table;

    instName = NULL;
    if (objc == 3) {
        instName = Tcl_GetString(objv[2]);
    }
    Tcl_DStringInit(&ds);
    if (instName == NULL) {
        instName = GenerateName(interp, "", "", &ds);
    } else {
        char *p;

        p = strstr(instName, "#auto");
        if (p != NULL) {
            *p = '\0';
            instName = GenerateName(interp, instName, p + 5, &ds);
            *p = '#';
        } else {
            Blt_ObjectName objName;

            /* 
             * Parse the command and put back so that it's in a consistent
             * format.  
             *
             *  t1         <current namespace>::t1
             *  n1::t1     <current namespace>::n1::t1
             *  ::t1       ::t1
             *  ::n1::t1   ::n1::t1
             */
            if (!Blt_ParseObjectName(interp, instName, &objName, 0)) {
                return TCL_ERROR;
            }
            instName = Blt_MakeQualifiedName(&objName, &ds);
            /* 
             * Check if the command already exists. 
             */
            if (Blt_CommandExists(interp, instName)) {
                Tcl_AppendResult(interp, "a command \"", instName,
                                 "\" already exists", (char *)NULL);
                goto error;
            }
            if (blt_table_exists(interp, instName)) {
                Tcl_AppendResult(interp, "a table \"", instName, 
                        "\" already exists", (char *)NULL);
                goto error;
            }
        } 
    } 
    if (instName == NULL) {
        goto error;
    }
    if (blt_table_create(interp, instName, &table) == TCL_OK) {
        NewTableCmd(interp, table, instName);
        Tcl_SetStringObj(Tcl_GetObjResult(interp), instName, -1);
        Tcl_DStringFree(&ds);
        return TCL_OK;
    }
 error:
    Tcl_DStringFree(&ds);
    return TCL_ERROR;
}

/*
 *---------------------------------------------------------------------------
 *
 * TableDestroyOp --
 *
 *      Deletes one or more instances of table objects.  The deletion
 *      process is done by deleting the TCL command associated with the
 *      object.
 *      
 * Results:
 *      A standard TCL result.  If one of the names given doesn't represent
 *      an instance, TCL_ERROR is returned and an error message is left in
 *      the interpreter result.
 *
 *      blt::datatable destroy ?tableName ...?
 *
 *---------------------------------------------------------------------------
 */
static int
TableDestroyOp(ClientData clientData, Tcl_Interp *interp, int objc,
               Tcl_Obj *const *objv)
{
    int i;

    for (i = 2; i < objc; i++) {
        Cmd *cmdPtr;

        cmdPtr = GetTableCmd(interp, Tcl_GetString(objv[i]));
        if (cmdPtr == NULL) {
            Tcl_AppendResult(interp, "can't find table \"", 
                Tcl_GetString(objv[i]), "\"", (char *)NULL);
            return TCL_ERROR;
        }
        Tcl_DeleteCommandFromToken(interp, cmdPtr->cmdToken);
    }
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * TableExistsOp --
 *
 *      Indicates if the table object exists.  
 *      
 * Results:
 *      A standard TCL result.  If one of the names given doesn't represent
 *      an instance, TCL_ERROR is returned and an error message is left in
 *      the interpreter result.
 *
 *      blt::datatable names tableName
 *
 *---------------------------------------------------------------------------
 */
static int
TableExistsOp(ClientData clientData, Tcl_Interp *interp, int objc,
               Tcl_Obj *const *objv)
{
    int bool;

    bool = FALSE;
    if (GetTableCmd(interp, Tcl_GetString(objv[2])) != NULL) {
        bool = TRUE;
    }
    Tcl_SetBooleanObj(Tcl_GetObjResult(interp), bool);
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * TableNamesOp --
 *
 *      Returns the names of all the table-object instances matching a
 *      given pattern.  If no pattern argument is provided, then all object
 *      names are returned.  The names returned are namespace qualified.
 *      
 * Results:
 *      Always returns TCL_OK.  The names of the matching objects is
 *      returned via the interpreter result.
 *
 *      blt::datatable names ?pattern ...?
 *
 *---------------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
TableNamesOp(ClientData clientData, Tcl_Interp *interp, int objc,
             Tcl_Obj *const *objv)
{
    TableCmdInterpData *dataPtr = clientData;
    Blt_HashEntry *hPtr;
    Blt_HashSearch iter;
    Tcl_Obj *listObjPtr;

    listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL);
    for (hPtr = Blt_FirstHashEntry(&dataPtr->instTable, &iter); hPtr != NULL;
         hPtr = Blt_NextHashEntry(&iter)) {
        const char *name;
        int match;
        int i;

        name = Blt_GetHashKey(&dataPtr->instTable, hPtr);
        match = TRUE;
        for (i = 2; i < objc; i++) {
            match = Tcl_StringMatch(name, Tcl_GetString(objv[i]));
            if (match) {
                break;
            }
        }
        if (!match) {
            continue;
        }
        Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewStringObj(name,-1));
    }
    Tcl_SetObjResult(interp, listObjPtr);
    return TCL_OK;
}

/*ARGSUSED*/
static int
TableLoadOp(ClientData clientData, Tcl_Interp *interp, int objc, 
            Tcl_Obj *const *objv)
{
    Blt_HashEntry *hPtr;
    TableCmdInterpData *dataPtr = clientData;
    Tcl_DString libName;
    const char *fmt;
    char *safeProcName, *initProcName;
    int result;
    int length;

    fmt = Tcl_GetStringFromObj(objv[2], &length);
    hPtr = Blt_FindHashEntry(&dataPtr->fmtTable, fmt);
    if (hPtr != NULL) {
        DataFormat *fmtPtr;

        fmtPtr = Blt_GetHashValue(hPtr);
        if (fmtPtr->flags & FMT_LOADED) {
            return TCL_OK;              /* Converter is already loaded. */
        }
    }
    Tcl_DStringInit(&libName);
    {
        Tcl_DString pathName;
        const char *path;

        Tcl_DStringInit(&pathName);
        path = Tcl_TranslateFileName(interp, Tcl_GetString(objv[3]), &pathName);
        if (path == NULL) {
            Tcl_DStringFree(&pathName);
            return TCL_ERROR;
        }
        Tcl_DStringAppend(&libName, path, -1);
        Tcl_DStringFree(&pathName);
    }

    initProcName = Blt_AssertMalloc(11 + length + 5 + 1);
    Blt_FmtString(initProcName, 11 + length + 5 + 1, "blt_table_%s_init", fmt);
    safeProcName = Blt_AssertMalloc(11 + length + 9 + 1);
    Blt_FmtString(safeProcName, 11 + length + 9+1, "blt_table_%s_safe_init", fmt);

    Tcl_DStringAppend(&libName, "/", -1);
    Tcl_UtfToTitle((char *)fmt);
    Tcl_DStringAppend(&libName, "Table", 5);
    Tcl_DStringAppend(&libName, fmt, -1);
    Tcl_DStringAppend(&libName, Blt_Itoa(BLT_MAJOR_VERSION), 1);
    Tcl_DStringAppend(&libName, Blt_Itoa(BLT_MINOR_VERSION), 1);
    Tcl_DStringAppend(&libName, BLT_LIB_SUFFIX, -1);
    Tcl_DStringAppend(&libName, BLT_SO_EXT, -1);

    result = Blt_LoadLibrary(interp, Tcl_DStringValue(&libName), initProcName, 
        safeProcName); 
    Tcl_DStringFree(&libName);
    if (safeProcName != NULL) {
        Blt_Free(safeProcName);
    }
    if (initProcName != NULL) {
        Blt_Free(initProcName);
    }
    return result;
}

/*
 *---------------------------------------------------------------------------
 *
 * TableObjCmd --
 *
 *---------------------------------------------------------------------------
 */
static Blt_OpSpec tableCmdOps[] =
{
    {"create",  1, TableCreateOp,  2, 3, "?tableName?",},
    {"destroy", 1, TableDestroyOp, 2, 0, "?tableName ...?",},
    {"exists",  1, TableExistsOp,  3, 3, "tableName",},
    {"load",    1, TableLoadOp,    4, 4, "tableName libpath",},
    {"names",   1, TableNamesOp,   2, 0, "?pattern ...?",},
};

static int numCmdOps = sizeof(tableCmdOps) / sizeof(Blt_OpSpec);

/*ARGSUSED*/
static int
TableObjCmd(ClientData clientData, Tcl_Interp *interp, int objc, 
            Tcl_Obj *const *objv)
{
    Tcl_ObjCmdProc *proc;

    proc = Blt_GetOpFromObj(interp, numCmdOps, tableCmdOps, BLT_OP_ARG1, objc, 
        objv, 0);
    if (proc == NULL) {
        return TCL_ERROR;
    }
    return (*proc) (clientData, interp, objc, objv);
}

/*
 *---------------------------------------------------------------------------
 *
 * TableInterpDeleteProc --
 *
 *      This is called when the interpreter registering the "datatable"
 *      command is deleted.
 *
 * Results:
 *      None.
 *
 * Side Effects:
 *      Removes the hash table managing all table names.
 *
 *---------------------------------------------------------------------------
 */
/* ARGSUSED */
static void
TableInterpDeleteProc(ClientData clientData, Tcl_Interp *interp)
{
    TableCmdInterpData *dataPtr = clientData;

    /* All table instances should already have been destroyed when their
     * respective TCL commands were deleted. */
    Blt_DeleteHashTable(&dataPtr->instTable);
    Blt_DeleteHashTable(&dataPtr->fmtTable);
    Blt_DeleteHashTable(&dataPtr->findTable);
    Tcl_DeleteAssocData(interp, TABLE_THREAD_KEY);
    Blt_Free(dataPtr);
}

/*
 *---------------------------------------------------------------------------
 *
 * Blt_TableCmdInitProc --
 *
 *      This procedure is invoked to initialize the "datatable" command.
 *
 * Results:
 *      None.
 *
 * Side Effects:
 *      Creates the new command and adds a new entry into a global Tcl
 *      associative array.
 *
 *---------------------------------------------------------------------------
 */
int
Blt_TableCmdInitProc(Tcl_Interp *interp)
{
    static Blt_CmdSpec cmdSpec = { "datatable", TableObjCmd, };
    DataFormat *fp, *fend;
    TableCmdInterpData *dataPtr;

    dataPtr = GetTableCmdInterpData(interp);
    cmdSpec.clientData = dataPtr;
    if (Blt_InitCmd(interp, "::blt", &cmdSpec) != TCL_OK) {
        return TCL_ERROR;
    }
    Blt_InitHashTable(&dataPtr->fmtTable, BLT_STRING_KEYS);
    for (fp = dataFormats, fend = fp + NUMFMTS; fp < fend; fp++) {
        Blt_HashEntry *hPtr;
        int isNew;

        hPtr = Blt_CreateHashEntry(&dataPtr->fmtTable, fp->name, &isNew);
        fp->flags |= FMT_STATIC;
        Blt_SetHashValue(hPtr, fp);
    }
    return TCL_OK;
}

/* Dump table to dbm */
/* Convert node data to datablock */

int
blt_table_register_format(Tcl_Interp *interp, const char *fmt, 
                         BLT_TABLE_IMPORT_PROC *importProc, 
                         BLT_TABLE_EXPORT_PROC *exportProc)
{
    Blt_HashEntry *hPtr;
    DataFormat *fmtPtr;
    TableCmdInterpData *dataPtr;
    int isNew;

    dataPtr = GetTableCmdInterpData(interp);
    hPtr = Blt_CreateHashEntry(&dataPtr->fmtTable, fmt, &isNew);
    if (isNew) {
        fmtPtr = Blt_AssertMalloc(sizeof(DataFormat));
        fmtPtr->name = Blt_AssertStrdup(fmt);
        Blt_SetHashValue(hPtr, fmtPtr);
    } else {
        fmtPtr = Blt_GetHashValue(hPtr);
    }
    fmtPtr->flags |= FMT_LOADED;
    fmtPtr->importProc = importProc;
    fmtPtr->exportProc = exportProc;
    return TCL_OK;
}

