root/tclBasic.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. Tcl_CreateInterp
  2. Tcl_WatchInterp
  3. Tcl_DeleteInterp
  4. Tcl_CreateCommand
  5. Tcl_DeleteCommand
  6. Tcl_Eval
  7. Tcl_CreateTrace
  8. Tcl_DeleteTrace
  9. Tcl_AddErrorInfo
  10. TclFindCmd

/* 
 * tclBasic.c --
 *
 *      Contains the basic facilities for TCL command interpretation,
 *      including interpreter creation and deletion, command creation
 *      and deletion, and command parsing and execution.
 *
 * Copyright 1987 Regents of the University of California
 * Permission to use, copy, modify, and distribute this
 * software and its documentation for any purpose and without
 * fee is hereby granted, provided that the above copyright
 * notice appear in all copies.  The University of California
 * makes no representations about the suitability of this
 * software for any purpose.  It is provided "as is" without
 * express or implied warranty.
 */

#ifndef lint
static char rcsid[] = "$Header: /sprite/src/lib/tcl/RCS/tclBasic.c,v 1.62 90/01/27 14:43:53 ouster Exp $ SPRITE (Berkeley)";
#endif /* not lint */

#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include "tclInt.h"

/*
 * Built-in commands, and the procedures associated with them:
 */

static char *builtInCmds[] = {
    "break",
    "case",
    "catch",
    "concat",
    "continue",
    "error",
    "eval",
    "exec",
    "expr",
    "file",
    "for",
    "foreach",
    "format",
#ifdef GNU
    "glob",
#endif
    "global",
    "if",
    "strchr",
    "info",
    "length",
    "list",
    "print",
    "proc",
    "range",
    "rename",
    "return",
    "scan",
    "set",
    "source",
    "string",
    "time",
    "uplevel",
    NULL
};

static int (*(builtInProcs[]))() = {
    Tcl_BreakCmd,
    Tcl_CaseCmd,
    Tcl_CatchCmd,
    Tcl_ConcatCmd,
    Tcl_ContinueCmd,
    Tcl_ErrorCmd,
    Tcl_EvalCmd,
    Tcl_ExecCmd,
    Tcl_ExprCmd,
    Tcl_FileCmd,
    Tcl_ForCmd,
    Tcl_ForeachCmd,
    Tcl_FormatCmd,
#ifdef GNU
    Tcl_GlobCmd,
#endif
    Tcl_GlobalCmd,
    Tcl_IfCmd,
    Tcl_IndexCmd,
    Tcl_InfoCmd,
    Tcl_LengthCmd,
    Tcl_ListCmd,
    Tcl_PrintCmd,
    Tcl_ProcCmd,
    Tcl_RangeCmd,
    Tcl_RenameCmd,
    Tcl_ReturnCmd,
    Tcl_ScanCmd,
    Tcl_SetCmd,
    Tcl_SourceCmd,
    Tcl_StringCmd,
    Tcl_TimeCmd,
    Tcl_UplevelCmd,
    NULL
};
 
/*
 *----------------------------------------------------------------------
 *
 * Tcl_CreateInterp --
 *
 *      Create a new TCL command interpreter.
 *
 * Results:
 *      The return value is a token for the interpreter, which may be
 *      used in calls to procedures like Tcl_CreateCmd, Tcl_Eval, or
 *      Tcl_DeleteInterp.
 *
 * Side effects:
 *      The command interpreter is initialized with an empty variable
 *      table and the built-in commands.
 *
 *----------------------------------------------------------------------
 */

Tcl_Interp *
Tcl_CreateInterp()
{
    register Interp *iPtr;
    register char **namePtr;
    register int (**procPtr)();
    register Command *cmdPtr;

    iPtr = (Interp *) ckalloc(sizeof(Interp));
    iPtr->result = iPtr->resultSpace;
    iPtr->dynamic = 0;
    iPtr->errorLine = 0;
    iPtr->commandPtr = NULL;
    iPtr->globalPtr = NULL;
    iPtr->numLevels = 0;
    iPtr->framePtr = NULL;
    iPtr->varFramePtr = NULL;
    iPtr->cmdCount = 0;
    iPtr->errInProgress = 0;
    iPtr->noEval = 0;
    iPtr->flags = 0;
    iPtr->tracePtr = NULL;
    iPtr->callbackPtr = NULL;
    iPtr->resultSpace[0] = 0;

    /*
     * Create the built-in commands.  Do it here, rather than calling
     * Tcl_CreateCommand, because it's faster (there's no need to
     * check for a pre-existing command by the same name).
     */

    for (namePtr = builtInCmds, procPtr = builtInProcs;
            *namePtr != NULL; namePtr++, procPtr++) {
        cmdPtr = (Command *) ckalloc(CMD_SIZE(strlen(*namePtr)));
        cmdPtr->proc = *procPtr;
        cmdPtr->clientData = (ClientData) NULL;
        cmdPtr->deleteProc = NULL;
        cmdPtr->nextPtr = iPtr->commandPtr;
        iPtr->commandPtr = cmdPtr;
        strcpy(cmdPtr->name, *namePtr);
    }

    return (Tcl_Interp *) iPtr;
}
 
/*
 *--------------------------------------------------------------
 *
 * Tcl_WatchInterp --
 *
 *      Arrange for a procedure to be called before a given
 *      interpreter is deleted.
 *
 * Results:
 *      None.
 *
 * Side effects:
 *      When Tcl_DeleteInterp is invoked to delete interp,
 *      proc will be invoked.  See the manual entry for
 *      details.
 *
 *--------------------------------------------------------------
 */

void
Tcl_WatchInterp(interp, proc, clientData)
    Tcl_Interp *interp;         /* Interpreter to watch. */
    void (*proc)();             /* Procedure to call when interpreter
                                 * is about to be deleted. */
    ClientData clientData;      /* One-word value to pass to proc. */
{
    register InterpCallback *icPtr;
    Interp *iPtr = (Interp *) interp;

    icPtr = (InterpCallback *) ckalloc(sizeof(InterpCallback));
    icPtr->proc = proc;
    icPtr->clientData = clientData;
    icPtr->nextPtr = iPtr->callbackPtr;
    iPtr->callbackPtr = icPtr;
}
 
/*
 *----------------------------------------------------------------------
 *
 * Tcl_DeleteInterp --
 *
 *      Delete an interpreter and ckfree up all of the resources associated
 *      with it.
 *
 * Results:
 *      None.
 *
 * Side effects:
 *      The interpreter is destroyed.  The caller should never again
 *      use the interp token.
 *
 *----------------------------------------------------------------------
 */

void
Tcl_DeleteInterp(interp)
    Tcl_Interp *interp;         /* Token for command interpreter (returned
                                 * by a previous call to Tcl_CreateInterp). */
{
    Interp *iPtr = (Interp *) interp;
    register Command *cmdPtr;
    register Trace *tracePtr;
    register InterpCallback *icPtr;

    /*
     * If the interpreter is in use, delay the deletion until later.
     */

    iPtr->flags |= DELETED;
    if (iPtr->numLevels != 0) {
        return;
    }

    /*
     * Invoke callbacks, if there's anyone who wants to know about
     * the interpreter deletion.
     */

    for (icPtr = iPtr->callbackPtr; icPtr != NULL;
            icPtr = icPtr->nextPtr) {
        (*icPtr->proc)(icPtr->clientData, interp);
        ckfree((char *) icPtr);
    }

    /*
     * Free up any remaining resources associated with the
     * interpreter.
     */

    for (cmdPtr = iPtr->commandPtr; cmdPtr != NULL;
            cmdPtr = cmdPtr->nextPtr) {
        if (cmdPtr->deleteProc != NULL) { 
            (*cmdPtr->deleteProc)(cmdPtr->clientData);
        }
        ckfree((char *) cmdPtr);
    }
    iPtr->commandPtr = NULL;
    TclDeleteVars(iPtr);
    for (tracePtr = iPtr->tracePtr; tracePtr != NULL;
            tracePtr = tracePtr->nextPtr) {
        ckfree((char *) tracePtr);
    }
    ckfree((char *) iPtr);
}
 
/*
 *----------------------------------------------------------------------
 *
 * Tcl_CreateCommand --
 *
 *      Define a new command in a command table.
 *
 * Results:
 *      None.
 *
 * Side effects:
 *      If a command named cmdName already exists for interp, it is
 *      deleted.  In the future, when cmdName is seen as the name of
 *      a command by Tcl_Eval, proc will be called with the following
 *      syntax:
 *
 *      int
 *      proc(clientData, interp, argc, argv)
 *          ClientData clientData;
 *          Tcl_Interp *interp;
 *          int argc;
 *          char **argv;
 *      {
 *      }
 *
 *      The clientData and interp arguments are the same as the corresponding
 *      arguments passed to this procedure.  Argc and argv describe the
 *      arguments to the command, in the usual UNIX fashion.  Proc must
 *      return a code like TCL_OK or TCL_ERROR.  It can also set interp->result
 *      ("" is the default value if proc doesn't set it) and interp->dynamic (0
 *      is the default).  See tcl.h for more information on these variables.
 *
 *      When the command is deleted from the table, deleteProc will be called
 *      in the following way:
 *
 *      void
 *      deleteProc(clientData)
 *          ClientData clientData;
 *      {
 *      }
 *
 *      DeleteProc allows command implementors to perform their own cleanup
 *      when commands (or interpreters) are deleted.
 *
 *----------------------------------------------------------------------
 */

void
Tcl_CreateCommand(interp, cmdName, proc, clientData, deleteProc)
    Tcl_Interp *interp;         /* Token for command interpreter (returned
                                 * by a previous call to Tcl_CreateInterp). */
    char *cmdName;              /* Name of command. */
    int (*proc)();              /* Command procedure to associate with
                                 * cmdName. */
    ClientData clientData;      /* Arbitrary one-word value to pass to proc. */
    void (*deleteProc)();       /* If not NULL, gives a procedure to call when
                                 * this command is deleted. */
{
    Interp *iPtr = (Interp *) interp;
    register Command *cmdPtr;

    Tcl_DeleteCommand(interp, cmdName);
    cmdPtr = (Command *) ckalloc(CMD_SIZE(strlen(cmdName)));
    cmdPtr->proc = proc;
    cmdPtr->clientData = clientData;
    cmdPtr->deleteProc = deleteProc;
    cmdPtr->nextPtr = iPtr->commandPtr;
    iPtr->commandPtr = cmdPtr;
    strcpy(cmdPtr->name, cmdName);
}
 
/*
 *----------------------------------------------------------------------
 *
 * Tcl_DeleteCommand --
 *
 *      Remove the given command from the given interpreter.
 *
 * Results:
 *      None.
 *
 * Side effects:
 *      CmdName will no longer be recognized as a valid command for
 *      interp.
 *
 *----------------------------------------------------------------------
 */

void
Tcl_DeleteCommand(interp, cmdName)
    Tcl_Interp *interp;         /* Token for command interpreter (returned
                                 * by a previous call to Tcl_CreateInterp). */
    char *cmdName;              /* Name of command to remove. */
{
    Interp *iPtr = (Interp *) interp;
    Command *cmdPtr;

    cmdPtr = TclFindCmd(iPtr, cmdName, 0);
    if (cmdPtr != NULL) {
        if (cmdPtr->deleteProc != NULL) {
            (*cmdPtr->deleteProc)(cmdPtr->clientData);
        }
        iPtr->commandPtr = cmdPtr->nextPtr;
        ckfree((char *) cmdPtr);
    }
}
 
/*
 *-----------------------------------------------------------------
 *
 * Tcl_Eval --
 *
 *      Parse and execute a command in the Tcl language.
 *
 * Results:
 *      The return value is one of the return codes defined in
 *      tcl.h (such as TCL_OK), and interp->result contains a string
 *      value to supplement the return code.  The value of interp->result
 *      will persist only until the next call to Tcl_Eval:  copy it
 *      or lose it!
 *
 * Side effects:
 *      Almost certainly;  depends on the command.
 *
 *-----------------------------------------------------------------
 */

int
Tcl_Eval(interp, cmd, flags, termPtr)
    Tcl_Interp *interp;         /* Token for command interpreter (returned
                                 * by a previous call to Tcl_CreateInterp). */
    char *cmd;                  /* Pointer to TCL command to interpret. */
    int flags;                  /* OR-ed combination of flags like
                                 * TCL_BRACKET_TERM. */
    char **termPtr;             /* If non-NULL, fill in the address it points
                                 * to with the address of the char. just after
                                 * the last one that was part of cmd.  See
                                 * the man page for details on this. */
{
    /*
     * While processing the command, make a local copy of
     * the command characters.  This is needed in order to
     * terminate each argument with a null character, replace
     * backslashed-characters, etc.  The copy starts out in
     * a static string (for speed) but gets expanded into
     * dynamically-allocated strings if necessary.  The constant
     * BUFFER indicates how much space there must be in the copy
     * in order to pass through the main loop below (e.g., must
     * have space to copy both a backslash and its following
     * characters).
     */

#   define NUM_CHARS 200
#   define BUFFER 5
    char copyStorage[NUM_CHARS];
    char *copy = copyStorage;   /* Pointer to current copy. */
    int copySize = NUM_CHARS;   /* Size of current copy. */
    register char *dst;         /* Points to next place to copy
                                 * a character. */
    char *limit;                /* When dst gets here, must make
                                 * the copy larger. */

    /*
     * This procedure generates an (argv, argc) array for the command,
     * It starts out with stack-allocated space but uses dynamically-
     * allocated storage to increase it if needed.
     */

#   define NUM_ARGS 10
    char *(argStorage[NUM_ARGS]);
    char **argv = argStorage;
    int argc;
    int argSize = NUM_ARGS;

    /*
     * Keep count of how many nested open braces or quotes there
     * are at the current point in the current argument.  If a
     * quoted argument is being read, then openQuote and openBraces
     * will both be 1.
     */

    int openBraces = 0;                 /* Curent nesting level. */
    int openQuote = 0;                  /* Non-zero means quoted arg
                                         * in progress. */

    register char *src;                 /* Points to current character
                                         * in cmd. */
    char termChar;                      /* Return when this character is found
                                         * (either ']' or '\0').  Zero means
                                         * that newlines terminate commands. */
    char *argStart;                     /* Location in cmd of first                                                      * non-separator character in
                                         * current argument;  it's
                                         * used to eliminate multiple
                                         * separators between args and
                                         * extra separators after last
                                         * arg in command. */
    int result = TCL_OK;                /* Return value. */
    int i;
    register Interp *iPtr = (Interp *) interp;
    Command *cmdPtr;
    char *tmp;
    char *dummy;                        /* Make termPtr point here if it was
                                         * originally NULL. */
    char *syntaxMsg;
    char *syntaxPtr;                    /* Points to "relevant" character
                                         * for syntax violations. */
    char *cmdStart;                     /* Points to first non-blank char. in
                                         * command (used in calling trace
                                         * procedures). */
    register Trace *tracePtr;

    /*
     * Set up the result so that if there's no command at all in
     * the string then this procedure will return TCL_OK.
     */

    if (iPtr->dynamic) {
        ckfree((char *) iPtr->result);
        iPtr->dynamic = 0;
    }
    iPtr->result = iPtr->resultSpace;
    iPtr->resultSpace[0] = 0;

    iPtr->numLevels++;
    iPtr->errInProgress = 0;
    src = cmd;
    result = TCL_OK;
    if (flags & TCL_BRACKET_TERM) {
        termChar = ']';
    } else {
        termChar = 0;
    }
    if (termPtr == NULL) {
        termPtr = &dummy;
    }

    /*
     * There can be many sub-commands (separated by semi-colons or
     * newlines) in one command string.  This outer loop iterates over
     * the inner commands.
     */

    for (*termPtr = src; *src != termChar; *termPtr = src) {

        /*
         * Skim off leading white space and semi-colons, and skip comments.
         */

        while (isspace(*src) || (*src == ';')) {
            src += 1;
        }
        if (*src == '#') {
            for (src++; *src != 0; src++) {
                if (*src == '\n') {
                    src++;
                    break;
                }
            }
            continue;
        }

        /*
         * Set up the first argument (the command name).  Note that
         * the arg pointer gets set up BEFORE the first real character
         * of the argument has been found.
         */
    
        dst = copy;
        argc = 0;
        limit = copy + copySize - BUFFER;
        argv[0] = dst;
        argStart = cmdStart = src;

        /*
         * Skim off the command name and arguments by looping over
         * characters and processing each one according to its type.
         */
    
        while (1) {
            switch (*src) {
    
                /*
                 * All braces are treated as normal characters
                 * unless the first character of the argument is an
                 * open brace.  In that case, braces nest and
                 * the argument terminates when all braces are matched.
                 * Internal braces are also copied like normal chars.
                 */
    
                case '{': {
                    if ((openBraces == 0) && (dst == argv[argc])) {
                        syntaxPtr = src;
                        openBraces = 1;
                        break;
                    }
                    *dst = '{'; dst++;
                    if ((openBraces > 0) && !openQuote) {
                        openBraces++;
                    }
                    break;
                }

                case '}': {
                    if (openBraces == 1) {
                        openBraces = 0;
                        if (!isspace(src[1]) && (src[1] != termChar) &&
                                (src[1] != 0) && (src[1] != ';')) {
                            syntaxPtr = src;
                            syntaxMsg = "extra characters after close-brace";
                            goto syntaxError;
                        }
                    } else {
                        *dst = '}'; dst++;
                        if ((openBraces > 0) && !openQuote) {
                            openBraces--;
                        }
                    }
                    break;
                }

                case '"': {
                    if (!openQuote) {
                        if ((openBraces) || (dst != argv[argc])) {
                            *dst = '"'; dst++;
                            break;
                        }
                        syntaxPtr = src;
                        openQuote = 1;
                        openBraces = 1;
                    } else {
                        openQuote = 0;
                        openBraces = 0;
                        if (!isspace(src[1]) && (src[1] != termChar) &&
                                (src[1] != 0)) {
                            syntaxPtr = src;
                            syntaxMsg = "extra characters after close-quote";
                            goto syntaxError;
                        }
                    }
                    break;
                }
    
                case '[': {
    
                    /*
                     * Open bracket: if not in middle of braces, then execute
                     * following command and substitute result into argument.
                     */

                    if (openBraces != 0) {
                        *dst = '['; dst++;
                    } else {
                        int length;
    
                        result = Tcl_Eval(interp, src+1, TCL_BRACKET_TERM,
                                &tmp);
                        src = tmp;
                        if (result != TCL_OK) {
                            goto done;
                        }
    
                        /*
                         * Copy the return value into the current argument.
                         * May have to enlarge the argument storage.  When
                         * enlarging, get more than enough to reduce the
                         * likelihood of having to enlarge again.  This code
                         * is used for $-processing also.
                         */

                        copyResult:
                        length = strlen(iPtr->result);
                        if ((limit - dst) < length) {
                            char *newCopy;
                            int bytes;

                            bytes = dst - copy;
                            copySize = length + 10 + bytes;
                            newCopy = (char *) ckalloc((unsigned) copySize);
                            move_argv(argv, argc, copy, newCopy, bytes);
                            dst = newCopy + bytes;
                            if (copy != copyStorage) {
                                ckfree((char *) copy);
                            }
                            copy = newCopy;
                            limit = newCopy + copySize - BUFFER;
                        }
                        bcopy(iPtr->result, dst, length);
                        dst += length;

                        /*
                         * Clear out the return value again.
                         */

                        if (iPtr->dynamic) {
                            ckfree((char *) iPtr->result);
                            iPtr->dynamic = 0;
                        }
                        iPtr->result = iPtr->resultSpace;
                        iPtr->resultSpace[0] = 0;
                    }
                    break;
                }

                case '$': {
                    if (openBraces != 0) {
                        *dst = '$'; dst++;
                    } else {
                        char *value;

                        /*
                         * Parse off a variable name and copy its value.
                         */
    
                        value = Tcl_ParseVar(interp, src, &tmp);
                        if (value == 0) {
                            result = TCL_ERROR;
                            goto done;
                        }
                        interp->result = value;
                        src = tmp-1;
                        goto copyResult;
                    }
                    break;
                }

                case ']': {
                    if ((openBraces == 0) && (termChar == ']')) {
                        goto cmdComplete;
                    }
                    *dst = ']'; dst++;
                    break;
                }

                case ';': {
                    if (openBraces == 0) {
                        goto cmdComplete;
                    }
                    *dst = *src; dst++;
                    break;
                }
    
                case '\n': {

                    /*
                     * A newline can be either a command terminator
                     * or a space character.  If it's a space character,
                     * just fall through to the space code below.
                     */
    
                    if ((openBraces == 0) && (termChar == 0)) {
                        goto cmdComplete;
                    }
                }

                case '\r':
                case ' ':
                case '\t': {
                    if (openBraces > 0) {
    
                        /*
                         * Quoted space.  Copy it into the argument.
                         */

                        *dst = *src; dst++;
                    } else {

                        /*
                         * Argument separator.  If there are many
                         * separators in a row (src == argStart) just
                         * ignore this separator.  Otherwise,
                         * Null-terminate the current argument and
                         * set up for the next one.
                         */

                        if (src == argStart) {
                            argStart = src+1;
                            break;
                        }
                        argStart = src+1;
                        *dst = 0;
                        dst++; argc++;

                        /*
                         * Make sure that the argument array is large enough
                         * for the next argument plus a final NULL argument
                         * pointer to terminate the list.
                         */

                        if (argc >= argSize-1) {
                            char **newArgs;
    
                            argSize *= 2;
                            newArgs = (char **)
                                    ckalloc((unsigned) argSize * sizeof(char *));
                            for (i = 0; i < argc; i++) {
                                newArgs[i] = argv[i];
                            }
                            if (argv != argStorage) {
                                ckfree((char *) argv);
                            }
                            argv = newArgs;
                        }
                        argv[argc] = dst;
                        break;
                    }
                    break;
                }
    
                case '\\': {
                    int numRead;

                    /*
                     * First of all, make the special check for
                     * backslash followed by newline.  This can't
                     * be processed in the normal fashion of
                     * Tcl_Backslash because is maps to "nothing",
                     * rather than to a character.
                     */

                    if (src[1] == '\n') {
                        if (argStart  == src) {
                            argStart += 2;
                        }
                        src++;
                        break;
                    }

                    /*
                     * If we're in an argument in braces then the
                     * backslash doesn't get collapsed.  However,
                     * whether we're in braces or not the characters
                     * inside the backslash sequence must not receive
                     * any additional processing:  make src point to
                     * the last character of the sequence.
                     */

                    *dst = Tcl_Backslash(src, &numRead);
                    if (openBraces > 0) {
                        for ( ; numRead > 0; src++, dst++, numRead--) {
                            *dst = *src;
                        }
                        src--;
                    } else {
                        src += numRead-1;
                        dst++;
                    }
                    break;
                }
    
                case 0: {

                    /*
                     * End of string.  Make sure that braces/quotes
                     * were properly matched.  Also, it's only legal
                     * to terminate a command by a null character if
                     * termChar is zero.
                     */

                    if (openBraces != 0) {
                        if (openQuote) {
                            syntaxMsg = "unmatched quote";
                        } else {
                            syntaxMsg = "unmatched brace";
                        }
                        goto syntaxError;
                    } else if (termChar == ']') {
                        syntaxPtr = cmd;
                        syntaxMsg = "missing close-bracket";
                        goto syntaxError;
                    }
                    goto cmdComplete;
                }
    
                default: {
                    *dst = *src; dst++;
                    break;
                }
            }
            src += 1;
    
            /*
             * Make sure that we're not running out of space in the
             * string copy area.  If we are, allocate a larger area
             * and copy the string.  Be sure to update all of the
             * relevant pointers too.
             */
    
            if (dst >= limit) {
                char *newCopy;
                int bytes;
    
                bytes = dst - copy;
                copySize *= 2;
                newCopy = (char *) ckalloc((unsigned) copySize);
                move_argv(argv, argc, copy, newCopy, bytes);
                dst = newCopy + bytes;
                if (copy != copyStorage) {
                    ckfree((char *) copy);
                }
                copy = newCopy;
                limit = newCopy + copySize - BUFFER;
            }
    
        }
    
        /*
         * Terminate the last argument and add a final NULL argument.  If
         * the interpreter has been deleted then return;  if there's no
         * command, then go on to the next iteration.
         */

        cmdComplete:
        if (iPtr->flags & DELETED) {
            goto done;
        }
        if (src != argStart) {
            *dst = 0;
            argc++;
        }
        if ((argc == 0) || iPtr->noEval) {
            continue;
        }
        argv[argc] = NULL;

        cmdPtr = TclFindCmd(iPtr, argv[0], 1);
        if (cmdPtr == NULL) {
            sprintf(iPtr->result,
                    "\"%.50s\" is an invalid command name %s",
                    argv[0], "or ambiguous abbreviation");
            result = TCL_ERROR;
            goto done;
        }

        /*
         * Call trace procedures, if any, then invoke the command.
         */

        for (tracePtr = iPtr->tracePtr; tracePtr != NULL;
                tracePtr = tracePtr->nextPtr) {
            char saved;

            if (tracePtr->level < iPtr->numLevels) {
                continue;
            }
            saved = *src;
            *src = 0;
            (*tracePtr->proc)(tracePtr->clientData, interp, iPtr->numLevels,
                    cmdStart, cmdPtr->proc, cmdPtr->clientData, argc, argv);
            *src = saved;
        }

        iPtr->cmdCount++;
        result = (*cmdPtr->proc)(cmdPtr->clientData, interp, argc, argv);
        if (result != TCL_OK) {
            break;
        }
    }

    /*
     * Free up any extra resources that were allocated.
     */

    done:
    if (copy != copyStorage) {
        ckfree((char *) copy);
    }
    if (argv != argStorage) {
        ckfree((char *) argv);
    }
    iPtr->numLevels--;
    if (iPtr->numLevels == 0) {
        if (result == TCL_RETURN) {
            result = TCL_OK;
        }
        if ((result != TCL_OK) && (result != TCL_ERROR)) {
            if (iPtr->dynamic) {
                ckfree(iPtr->result);
                iPtr->dynamic = 0;
            }
            if (result == TCL_BREAK) {
                iPtr->result = "invoked \"break\" outside of a loop";
            } else if (result == TCL_CONTINUE) {
                iPtr->result = "invoked \"continue\" outside of a loop";
            } else {
                iPtr->result = iPtr->resultSpace;
                sprintf(iPtr->resultSpace, "command returned bad code: %d",
                        result);
            }
            result = TCL_ERROR;
        }
        if (iPtr->flags & DELETED) {
            Tcl_DeleteInterp(interp);
        }
    }

    /*
     * If an error occurred, record information about what was being
     * executed when the error occurred.
     */

    if (result == TCL_ERROR) {
        int numChars;
        register char *p;
        char *ellipsis;

        /*
         * Compute the line number where the error occurred.
         */

        iPtr->errorLine = 1;
        for (p = cmd; p != cmdStart; p++) {
            if (*p == '\n') {
                iPtr->errorLine++;
            }
        }
        for ( ; isspace(*p) || (*p == ';'); p++) {
            if (*p == '\n') {
                iPtr->errorLine++;
            }
        }

        /*
         * Figure out how much of the command to print in the error
         * message (up to a certain number of characters, or up to
         * the first new-line).
         */

        ellipsis = "";
        p = strchr(cmdStart, '\n');
        if (p == NULL) {
            numChars = strlen(cmdStart);
        } else {
            if (p < src) {
                ellipsis = "...";
            }
            numChars = p - cmdStart;
        }
        if (numChars > 40) {
            numChars = 40;
            ellipsis = "...";
        }

        if (!iPtr->errInProgress) {
            /*
             * This is the first piece of information being recorded
             * for this error.  Log the error message as well as the
             * command being executed.
             */

            if (strlen(iPtr->result) < 50) {
                sprintf(copyStorage,
                        "%s, while executing\n\"%.*s%s\"",
                        iPtr->result, numChars, cmdStart, ellipsis);
            } else {
                sprintf(copyStorage,
                        "%.50s..., while executing\n\"%.*s%s\"",
                        iPtr->result, numChars, cmdStart, ellipsis);
            }
        } else {
            sprintf(copyStorage, ", invoked from within\n\"%.*s%s\"",
                    numChars, cmdStart, ellipsis);
        }
        Tcl_AddErrorInfo(interp, copyStorage);
    }
    return result;

    /*
     * Syntax error:  generate an error message.
     */

    syntaxError: {
        char *first, *last;

        Tcl_Return(interp, (char *) NULL, TCL_STATIC);
        for (first = syntaxPtr; ((first != cmd) && (first[-1] != '\n'));
                first--) {
            /* Null loop body. */
        }
        for (last = syntaxPtr; ((*last != 0) && (*last!= '\n')); last++) {
            /* Null loop body. */
        }
        if ((syntaxPtr - first) > 60) {
            first = syntaxPtr - 60;
        }
        if ((last - first) > 70) {
            last = first + 70;
        }
        if (last == first) {
            sprintf(iPtr->resultSpace, "%s", syntaxMsg);
        } else {
            sprintf(iPtr->resultSpace, "%s: '%.*s => %.*s'", syntaxMsg,
                    syntaxPtr-first, first, last-syntaxPtr, syntaxPtr);
        }
        result = TCL_ERROR;
        iPtr->result = iPtr->resultSpace;
    }

    goto done;
}
 
/*
 *----------------------------------------------------------------------
 *
 * Tcl_CreateTrace --
 *
 *      Arrange for a procedure to be called to trace command execution.
 *
 * Results:
 *      The return value is a token for the trace, which may be passed
 *      to Tcl_DeleteTrace to eliminate the trace.
 *
 * Side effects:
 *      From now on, proc will be called just before a command procedure
 *      is called to execute a Tcl command.  Calls to proc will have the
 *      following form:
 *
 *      void
 *      proc(clientData, interp, level, command, cmdProc, cmdClientData,
 *              argc, argv)
 *          ClientData clientData;
 *          Tcl_Interp *interp;
 *          int level;
 *          char *command;
 *          int (*cmdProc)();
 *          ClientData cmdClientData;
 *          int argc;
 *          char **argv;
 *      {
 *      }
 *
 *      The clientData and interp arguments to proc will be the same
 *      as the corresponding arguments to this procedure.  Level gives
 *      the nesting level of command interpretation for this interpreter
 *      (0 corresponds to top level).  Command gives the ASCII text of
 *      the raw command, cmdProc and cmdClientData give the procedure that
 *      will be called to process the command and the ClientData value it
 *      will receive, and argc and argv give the arguments to the
 *      command, after any argument parsing and substitution.  Proc
 *      does not return a value.
 *
 *----------------------------------------------------------------------
 */

Tcl_Trace
Tcl_CreateTrace(interp, level, proc, clientData)
    Tcl_Interp *interp;         /* Interpreter in which to create the trace. */
    int level;                  /* Only call proc for commands at nesting level
                                 * <= level (1 => top level). */
    void (*proc)();             /* Procedure to call before executing each
                                 * command. */
    ClientData clientData;      /* Arbitrary one-word value to pass to proc. */
{
    register Trace *tracePtr;
    register Interp *iPtr = (Interp *) interp;

    tracePtr = (Trace *) ckalloc(sizeof(Trace));
    tracePtr->level = level;
    tracePtr->proc = proc;
    tracePtr->clientData = clientData;
    tracePtr->nextPtr = iPtr->tracePtr;
    iPtr->tracePtr = tracePtr;

    return (Tcl_Trace) tracePtr;
}
 
/*
 *----------------------------------------------------------------------
 *
 * Tcl_DeleteTrace --
 *
 *      Remove a trace.
 *
 * Results:
 *      None.
 *
 * Side effects:
 *      From now on there will be no more calls to the procedure given
 *      in trace.
 *
 *----------------------------------------------------------------------
 */

void
Tcl_DeleteTrace(interp, trace)
    Tcl_Interp *interp;         /* Interpreter that contains trace. */
    Tcl_Trace trace;            /* Token for trace (returned previously by
                                 * Tcl_CreateTrace). */
{
    register Interp *iPtr = (Interp *) interp;
    register Trace *tracePtr = (Trace *) trace;
    register Trace *tracePtr2;

    if (iPtr->tracePtr == tracePtr) {
        iPtr->tracePtr = tracePtr->nextPtr;
        ckfree((char *) tracePtr);
    } else {
        for (tracePtr2 = iPtr->tracePtr; tracePtr2 != NULL;
                tracePtr2 = tracePtr2->nextPtr) {
            if (tracePtr2->nextPtr == tracePtr) {
                tracePtr2->nextPtr = tracePtr->nextPtr;
                ckfree((char *) tracePtr);
                return;
            }
        }
    }
}
 
/*
 *----------------------------------------------------------------------
 *
 * Tcl_AddErrorInfo --
 *
 *      Add information to a message being accumulated that describes
 *      the current error.
 *
 * Results:
 *      None.
 *
 * Side effects:
 *      The contents of message are added to the "errorInfo" variable.
 *      If Tcl_Eval has been called since the current value of errorInfo
 *      was set, errorInfo is cleared before adding the new message.
 *
 *----------------------------------------------------------------------
 */

void
Tcl_AddErrorInfo(interp, message)
    Tcl_Interp *interp;         /* Interpreter to which error information
                                 * pertains. */
    char *message;              /* Message to record. */
{
    register Interp *iPtr = (Interp *) interp;

    if (iPtr->errInProgress) {
        int length;
        char *buffer, *oldVar;

        oldVar = Tcl_GetVar(interp, "errorInfo", 1);
        if(!oldVar) oldVar = "";
        length = strlen(oldVar);
        buffer = (char *)ckalloc((unsigned) (length + strlen(message) + 1));
        strcpy(buffer, oldVar);
        strcpy(buffer+length, message);
        Tcl_SetVar(interp, "errorInfo", buffer, 1);
    } else {
        iPtr->errInProgress = 1;
        Tcl_SetVar(interp, "errorInfo", message, 1);
    }
}
 
/*
 *----------------------------------------------------------------------
 *
 * TclFindCmd --
 *
 *      Find a particular command in an interpreter.
 *
 * Results:
 *      If the command doesn't exist in the table, or if it is an
 *      ambiguous abbreviation, then NULL is returned.  Otherwise
 *      the return value is a pointer to the command.  Unique
 *      abbreviations are allowed if abbrevOK is non-zero, but
 *      abbreviations take longer to look up (must scan the whole
 *      table twice).
 *
 * Side effects:
 *      If the command is found and is an exact match, it is relinked
 *      at the front of iPtr's command list so it will be found more
 *      quickly in the future.
 *
 *----------------------------------------------------------------------
 */

Command *
TclFindCmd(iPtr, cmdName, abbrevOK)
    Interp *iPtr;               /* Interpreter in which to search. */
    char *cmdName;              /* Desired command. */
    int abbrevOK;               /* Non-zero means permit abbreviations.
                                 * Zero means exact matches only. */
{
    register Command *prev;
    register Command *cur;
    register char c;
    Command *match;
    int length;

    /*
     * First check for an exact match.
     */

    c = *cmdName;
    for (prev = NULL, cur = iPtr->commandPtr; cur != NULL;
            prev = cur, cur = cur->nextPtr) {

        /*
         * Check the first character here before wasting time calling
         * strcmp.
         */

        if ((cur->name[0] == c) && (strcmp(cur->name, cmdName) == 0)) {
            if (prev != NULL) {
                prev->nextPtr = cur->nextPtr;
                cur->nextPtr = iPtr->commandPtr;
                iPtr->commandPtr = cur;
            }
            return cur;
        }
    }
    if (!abbrevOK) {
        return NULL;
    }

    /*
     * No exact match.  Make a second pass to check for a unique
     * abbreviation.  Don't bother to pull the matching entry to
     * the front of the list, since we have to search the whole list
     * for abbreviations anyway.
     */

    length = strlen(cmdName);
    match = NULL;
    for (prev = NULL, cur = iPtr->commandPtr; cur != NULL;
            prev = cur, cur = cur->nextPtr) {
        if ((cur->name[0] == c) && (strncmp(cur->name, cmdName, length) == 0)) {
            if (match != NULL) {
                return NULL;
            }
            match = cur;
        }
    }
    return match;
}


/* [<][>][^][v][top][bottom][index][help] */