home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 10 Tools / 10-Tools.zip / tcl2-73c.zip / tcl7.3 / tclHistory.c < prev    next >
C/C++ Source or Header  |  1993-10-13  |  31KB  |  1,110 lines

  1. /* 
  2.  * tclHistory.c --
  3.  *
  4.  *    This module implements history as an optional addition to Tcl.
  5.  *    It can be called to record commands ("events") before they are
  6.  *    executed, and it provides a command that may be used to perform
  7.  *    history substitutions.
  8.  *
  9.  * Copyright (c) 1990-1993 The Regents of the University of California.
  10.  * All rights reserved.
  11.  *
  12.  * Permission is hereby granted, without written agreement and without
  13.  * license or royalty fees, to use, copy, modify, and distribute this
  14.  * software and its documentation for any purpose, provided that the
  15.  * above copyright notice and the following two paragraphs appear in
  16.  * all copies of this software.
  17.  * 
  18.  * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
  19.  * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
  20.  * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
  21.  * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  22.  *
  23.  * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
  24.  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
  25.  * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
  26.  * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
  27.  * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
  28.  */
  29.  
  30. #ifndef lint
  31. static char rcsid[] = "$Header: /user6/ouster/tcl/RCS/tclHistory.c,v 1.30 93/10/13 13:05:38 ouster Exp $ SPRITE (Berkeley)";
  32. #endif /* not lint */
  33.  
  34. #include "tclInt.h"
  35.  
  36. /*
  37.  * This history stuff is mostly straightforward, except for one thing
  38.  * that makes everything very complicated.  Suppose that the following
  39.  * commands get executed:
  40.  *    echo foo
  41.  *    history redo
  42.  * It's important that the history event recorded for the second command
  43.  * be "echo foo", not "history redo".  Otherwise, if another "history redo"
  44.  * command is typed, it will result in infinite recursions on the
  45.  * "history redo" command.  Thus, the actual recorded history must be
  46.  *    echo foo
  47.  *    echo foo
  48.  * To do this, the history command revises recorded history as part of
  49.  * its execution.  In the example above, when "history redo" starts
  50.  * execution, the current event is "history redo", but the history
  51.  * command arranges for the current event to be changed to "echo foo".
  52.  *
  53.  * There are three additional complications.  The first is that history
  54.  * substitution may only be part of a command, as in the following
  55.  * command sequence:
  56.  *    echo foo bar
  57.  *    echo [history word 3]
  58.  * In this case, the second event should be recorded as "echo bar".  Only
  59.  * part of the recorded event is to be modified.  Fortunately, Tcl_Eval
  60.  * helps with this by recording (in the evalFirst and evalLast fields of
  61.  * the intepreter) the location of the command being executed, so the
  62.  * history module can replace exactly the range of bytes corresponding
  63.  * to the history substitution command.
  64.  *
  65.  * The second complication is that there are two ways to revise history:
  66.  * replace a command, and replace the result of a command.  Consider the
  67.  * two examples below:
  68.  *    format {result is %d} $num       |    format {result is %d} $num
  69.  *    print [history redo]           |    print [history word 3]
  70.  * Recorded history for these two cases should be as follows:
  71.  *    format {result is %d} $num       |    format {result is %d} $num
  72.  *    print [format {result is %d} $num] |    print $num
  73.  * In the left case, the history command was replaced with another command
  74.  * to be executed (the brackets were retained), but in the case on the
  75.  * right the result of executing the history command was replaced (i.e.
  76.  * brackets were replaced too).
  77.  *
  78.  * The third complication is that there could potentially be many
  79.  * history substitutions within a single command, as in:
  80.  *    echo [history word 3] [history word 2]
  81.  * There could even be nested history substitutions, as in:
  82.  *    history subs abc [history word 2]
  83.  * If history revisions were made immediately during each "history" command
  84.  * invocations, it would be very difficult to produce the correct cumulative
  85.  * effect from several substitutions in the same command.  To get around
  86.  * this problem, the actual history revision isn't made during the execution
  87.  * of the "history" command.  Information about the changes is just recorded,
  88.  * in xxx records, and the actual changes are made during the next call to
  89.  * Tcl_RecordHistory (when we know that execution of the previous command
  90.  * has finished).
  91.  */
  92.  
  93. /*
  94.  * Default space allocation for command strings:
  95.  */
  96.  
  97. #define INITIAL_CMD_SIZE 40
  98.  
  99. /*
  100.  * Forward declarations for procedures defined later in this file:
  101.  */
  102.  
  103. static void        DoRevs _ANSI_ARGS_((Interp *iPtr));
  104. static HistoryEvent *    GetEvent _ANSI_ARGS_((Interp *iPtr, char *string));
  105. static char *        GetWords _ANSI_ARGS_((Interp *iPtr, char *command,
  106.                 char *words));
  107. static void        InitHistory _ANSI_ARGS_((Interp *iPtr));
  108. static void        InsertRev _ANSI_ARGS_((Interp *iPtr,
  109.                 HistoryRev *revPtr));
  110. static void        MakeSpace _ANSI_ARGS_((HistoryEvent *hPtr, int size));
  111. static void        RevCommand _ANSI_ARGS_((Interp *iPtr, char *string));
  112. static void        RevResult _ANSI_ARGS_((Interp *iPtr, char *string));
  113. static int        SubsAndEval _ANSI_ARGS_((Interp *iPtr, char *cmd,
  114.                 char *old, char *new));
  115.  
  116. /*
  117.  *----------------------------------------------------------------------
  118.  *
  119.  * InitHistory --
  120.  *
  121.  *    Initialize history-related state in an interpreter.
  122.  *
  123.  * Results:
  124.  *    None.
  125.  *
  126.  * Side effects:
  127.  *    History info is initialized in iPtr.
  128.  *
  129.  *----------------------------------------------------------------------
  130.  */
  131.  
  132. static void
  133. InitHistory(iPtr)
  134.     register Interp *iPtr;        /* Interpreter to initialize. */
  135. {
  136.     int i;
  137.  
  138.     if (iPtr->numEvents != 0) {
  139.     return;
  140.     }
  141.     iPtr->numEvents = 20;
  142.     iPtr->events = (HistoryEvent *)
  143.         ckalloc((unsigned) (iPtr->numEvents * sizeof(HistoryEvent)));
  144.     for (i = 0; i < iPtr->numEvents; i++) {
  145.     iPtr->events[i].command = (char *) ckalloc(INITIAL_CMD_SIZE);
  146.     *iPtr->events[i].command = 0;
  147.     iPtr->events[i].bytesAvl = INITIAL_CMD_SIZE;
  148.     }
  149.     iPtr->curEvent = 0;
  150.     iPtr->curEventNum = 0;
  151. }
  152.  
  153. /*
  154.  *----------------------------------------------------------------------
  155.  *
  156.  * Tcl_RecordAndEval --
  157.  *
  158.  *    This procedure adds its command argument to the current list of
  159.  *    recorded events and then executes the command by calling Tcl_Eval.
  160.  *
  161.  * Results:
  162.  *    The return value is a standard Tcl return value, the result of
  163.  *    executing cmd.
  164.  *
  165.  * Side effects:
  166.  *    The command is recorded and executed.  In addition, pending history
  167.  *    revisions are carried out, and information is set up to enable
  168.  *    Tcl_Eval to identify history command ranges.  This procedure also
  169.  *    initializes history information for the interpreter, if it hasn't
  170.  *    already been initialized.
  171.  *
  172.  *----------------------------------------------------------------------
  173.  */
  174.  
  175. int
  176. Tcl_RecordAndEval(interp, cmd, flags)
  177.     Tcl_Interp *interp;        /* Token for interpreter in which command
  178.                  * will be executed. */
  179.     char *cmd;            /* Command to record. */
  180.     int flags;            /* Additional flags to pass to Tcl_Eval. 
  181.                  * TCL_NO_EVAL means only record: don't
  182.                  * execute command. */
  183. {
  184.     register Interp *iPtr = (Interp *) interp;
  185.     register HistoryEvent *eventPtr;
  186.     int length, result;
  187.  
  188.     if (iPtr->numEvents == 0) {
  189.     InitHistory(iPtr);
  190.     }
  191.     DoRevs(iPtr);
  192.  
  193.     /*
  194.      * Don't record empty commands.
  195.      */
  196.  
  197.     while (isspace(UCHAR(*cmd))) {
  198.     cmd++;
  199.     }
  200.     if (*cmd == '\0') {
  201.     Tcl_ResetResult(interp);
  202.     return TCL_OK;
  203.     }
  204.  
  205.     iPtr->curEventNum++;
  206.     iPtr->curEvent++;
  207.     if (iPtr->curEvent >= iPtr->numEvents) {
  208.     iPtr->curEvent = 0;
  209.     }
  210.     eventPtr = &iPtr->events[iPtr->curEvent];
  211.  
  212.     /*
  213.      * Chop off trailing newlines before recording the command.
  214.      */
  215.  
  216.     length = strlen(cmd);
  217.     while (cmd[length-1] == '\n') {
  218.     length--;
  219.     }
  220.     MakeSpace(eventPtr, length + 1);
  221.     strncpy(eventPtr->command, cmd, length);
  222.     eventPtr->command[length] = 0;
  223.  
  224.     /*
  225.      * Execute the command.  Note: history revision isn't possible after
  226.      * a nested call to this procedure, because the event at the top of
  227.      * the history list no longer corresponds to what's going on when
  228.      * a nested call here returns.  Thus, must leave history revision
  229.      * disabled when we return.
  230.      */
  231.  
  232.     result = TCL_OK;
  233.     if (flags != TCL_NO_EVAL) {
  234.     iPtr->historyFirst = cmd;
  235.     iPtr->revDisables = 0;
  236.     iPtr->evalFlags = flags | TCL_RECORD_BOUNDS;
  237.     result = Tcl_Eval(interp, cmd);
  238.     }
  239.     iPtr->revDisables = 1;
  240.     return result;
  241. }
  242.  
  243. /*
  244.  *----------------------------------------------------------------------
  245.  *
  246.  * Tcl_HistoryCmd --
  247.  *
  248.  *    This procedure is invoked to process the "history" Tcl command.
  249.  *    See the user documentation for details on what it does.
  250.  *
  251.  * Results:
  252.  *    A standard Tcl result.
  253.  *
  254.  * Side effects:
  255.  *    See the user documentation.
  256.  *
  257.  *----------------------------------------------------------------------
  258.  */
  259.  
  260.     /* ARGSUSED */
  261. int
  262. Tcl_HistoryCmd(dummy, interp, argc, argv)
  263.     ClientData dummy;            /* Not used. */
  264.     Tcl_Interp *interp;            /* Current interpreter. */
  265.     int argc;                /* Number of arguments. */
  266.     char **argv;            /* Argument strings. */
  267. {
  268.     register Interp *iPtr = (Interp *) interp;
  269.     register HistoryEvent *eventPtr;
  270.     int length;
  271.     char c;
  272.  
  273.     if (iPtr->numEvents == 0) {
  274.     InitHistory(iPtr);
  275.     }
  276.  
  277.     /*
  278.      * If no arguments, treat the same as "history info".
  279.      */
  280.  
  281.     if (argc == 1) {
  282.     goto infoCmd;
  283.     }
  284.  
  285.     c = argv[1][0];
  286.     length = strlen(argv[1]);
  287.  
  288.     if ((c == 'a') && (strncmp(argv[1], "add", length)) == 0) {
  289.     if ((argc != 3) && (argc != 4)) {
  290.         Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
  291.             " add event ?exec?\"", (char *) NULL);
  292.         return TCL_ERROR;
  293.     }
  294.     if (argc == 4) {
  295.         if (strncmp(argv[3], "exec", strlen(argv[3])) != 0) {
  296.         Tcl_AppendResult(interp, "bad argument \"", argv[3],
  297.             "\": should be \"exec\"", (char *) NULL);
  298.         return TCL_ERROR;
  299.         }
  300.         return Tcl_RecordAndEval(interp, argv[2], 0);
  301.     }
  302.     return Tcl_RecordAndEval(interp, argv[2], TCL_NO_EVAL);
  303.     } else if ((c == 'c') && (strncmp(argv[1], "change", length)) == 0) {
  304.     if ((argc != 3) && (argc != 4)) {
  305.         Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
  306.             " change newValue ?event?\"", (char *) NULL);
  307.         return TCL_ERROR;
  308.     }
  309.     if (argc == 3) {
  310.         eventPtr = &iPtr->events[iPtr->curEvent];
  311.         iPtr->revDisables += 1;
  312.         while (iPtr->revPtr != NULL) {
  313.         HistoryRev *nextPtr;
  314.  
  315.         ckfree(iPtr->revPtr->newBytes);
  316.         nextPtr = iPtr->revPtr->nextPtr;
  317.         ckfree((char *) iPtr->revPtr);
  318.         iPtr->revPtr = nextPtr;
  319.         }
  320.     } else {
  321.         eventPtr = GetEvent(iPtr, argv[3]);
  322.         if (eventPtr == NULL) {
  323.         return TCL_ERROR;
  324.         }
  325.     }
  326.     MakeSpace(eventPtr, strlen(argv[2]) + 1);
  327.     strcpy(eventPtr->command, argv[2]);
  328.     return TCL_OK;
  329.     } else if ((c == 'e') && (strncmp(argv[1], "event", length)) == 0) {
  330.     if (argc > 3) {
  331.         Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
  332.             " event ?event?\"", (char *) NULL);
  333.         return TCL_ERROR;
  334.     }
  335.     eventPtr = GetEvent(iPtr, argc==2 ? "-1" : argv[2]);
  336.     if (eventPtr == NULL) {
  337.         return TCL_ERROR;
  338.     }
  339.     RevResult(iPtr, eventPtr->command);
  340.     Tcl_SetResult(interp, eventPtr->command, TCL_VOLATILE);
  341.     return TCL_OK;
  342.     } else if ((c == 'i') && (strncmp(argv[1], "info", length)) == 0) {
  343.     int count, indx, i;
  344.     char *newline;
  345.  
  346.     if ((argc != 2) && (argc != 3)) {
  347.         Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
  348.             " info ?count?\"", (char *) NULL);
  349.         return TCL_ERROR;
  350.     }
  351.     infoCmd:
  352.     if (argc == 3) {
  353.         if (Tcl_GetInt(interp, argv[2], &count) != TCL_OK) {
  354.         return TCL_ERROR;
  355.         }
  356.         if (count > iPtr->numEvents) {
  357.         count = iPtr->numEvents;
  358.         }
  359.     } else {
  360.         count = iPtr->numEvents;
  361.     }
  362.     newline = "";
  363.     for (i = 0, indx = iPtr->curEvent + 1 + iPtr->numEvents - count;
  364.         i < count; i++, indx++) {
  365.         char *cur, *next, savedChar;
  366.         char serial[20];
  367.  
  368.         if (indx >= iPtr->numEvents) {
  369.         indx -= iPtr->numEvents;
  370.         }
  371.         cur = iPtr->events[indx].command;
  372.         if (*cur == '\0') {
  373.         continue;        /* No command recorded here. */
  374.         }
  375.         sprintf(serial, "%6d  ", iPtr->curEventNum + 1 - (count - i));
  376.         Tcl_AppendResult(interp, newline, serial, (char *) NULL);
  377.         newline = "\n";
  378.  
  379.         /*
  380.          * Tricky formatting here:  for multi-line commands, indent
  381.          * the continuation lines.
  382.          */
  383.  
  384.         while (1) {
  385.         next = strchr(cur, '\n');
  386.         if (next == NULL) {
  387.             break;
  388.         }
  389.         next++;
  390.         savedChar = *next;
  391.         *next = 0;
  392.         Tcl_AppendResult(interp, cur, "\t", (char *) NULL);
  393.         *next = savedChar;
  394.         cur = next;
  395.         }
  396.         Tcl_AppendResult(interp, cur, (char *) NULL);
  397.     }
  398.     return TCL_OK;
  399.     } else if ((c == 'k') && (strncmp(argv[1], "keep", length)) == 0) {
  400.     int count, i, src;
  401.     HistoryEvent *events;
  402.  
  403.     if (argc != 3) {
  404.         Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
  405.             " keep number\"", (char *) NULL);
  406.         return TCL_ERROR;
  407.     }
  408.     if (Tcl_GetInt(interp, argv[2], &count) != TCL_OK) {
  409.         return TCL_ERROR;
  410.     }
  411.     if ((count <= 0) || (count > 1000)) {
  412.         Tcl_AppendResult(interp, "illegal keep count \"", argv[2],
  413.             "\"", (char *) NULL);
  414.         return TCL_ERROR;
  415.     }
  416.  
  417.     /*
  418.      * Create a new history array and copy as much existing history
  419.      * as possible from the old array.
  420.      */
  421.  
  422.     events = (HistoryEvent *)
  423.         ckalloc((unsigned) (count * sizeof(HistoryEvent)));
  424.     if (count < iPtr->numEvents) {
  425.         src = iPtr->curEvent + 1 - count;
  426.         if (src < 0) {
  427.         src += iPtr->numEvents;
  428.         }
  429.     } else {
  430.         src = iPtr->curEvent + 1;
  431.     }
  432.     for (i = 0; i < count; i++, src++) {
  433.         if (src >= iPtr->numEvents) {
  434.         src = 0;
  435.         }
  436.         if (i < iPtr->numEvents) {
  437.         events[i] = iPtr->events[src];
  438.         iPtr->events[src].command = NULL;
  439.         } else {
  440.         events[i].command = (char *) ckalloc(INITIAL_CMD_SIZE);
  441.         events[i].command[0] = 0;
  442.         events[i].bytesAvl = INITIAL_CMD_SIZE;
  443.         }
  444.     }
  445.  
  446.     /*
  447.      * Throw away everything left in the old history array, and
  448.      * substitute the new one for the old one.
  449.      */
  450.  
  451.     for (i = 0; i < iPtr->numEvents; i++) {
  452.         if (iPtr->events[i].command != NULL) {
  453.         ckfree(iPtr->events[i].command);
  454.         }
  455.     }
  456.     ckfree((char *) iPtr->events);
  457.     iPtr->events = events;
  458.     if (count < iPtr->numEvents) {
  459.         iPtr->curEvent = count-1;
  460.     } else {
  461.         iPtr->curEvent = iPtr->numEvents-1;
  462.     }
  463.     iPtr->numEvents = count;
  464.     return TCL_OK;
  465.     } else if ((c == 'n') && (strncmp(argv[1], "nextid", length)) == 0) {
  466.     if (argc != 2) {
  467.         Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
  468.             " nextid\"", (char *) NULL);
  469.         return TCL_ERROR;
  470.     }
  471.     sprintf(iPtr->result, "%d", iPtr->curEventNum+1);
  472.     return TCL_OK;
  473.     } else if ((c == 'r') && (strncmp(argv[1], "redo", length)) == 0) {
  474.     if (argc > 3) {
  475.         Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
  476.             " redo ?event?\"", (char *) NULL);
  477.         return TCL_ERROR;
  478.     }
  479.     eventPtr = GetEvent(iPtr, argc==2 ? "-1" : argv[2]);
  480.     if (eventPtr == NULL) {
  481.         return TCL_ERROR;
  482.     }
  483.     RevCommand(iPtr, eventPtr->command);
  484.     return Tcl_Eval(interp, eventPtr->command);
  485.     } else if ((c == 's') && (strncmp(argv[1], "substitute", length)) == 0) {
  486.     if ((argc > 5) || (argc < 4)) {
  487.         Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
  488.             " substitute old new ?event?\"", (char *) NULL);
  489.         return TCL_ERROR;
  490.     }
  491.     eventPtr = GetEvent(iPtr, argc==4 ? "-1" : argv[4]);
  492.     if (eventPtr == NULL) {
  493.         return TCL_ERROR;
  494.     }
  495.     return SubsAndEval(iPtr, eventPtr->command, argv[2], argv[3]);
  496.     } else if ((c == 'w') && (strncmp(argv[1], "words", length)) == 0) {
  497.     char *words;
  498.  
  499.     if ((argc != 3) && (argc != 4)) {
  500.         Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
  501.             " words num-num/pat ?event?\"", (char *) NULL);
  502.         return TCL_ERROR;
  503.     }
  504.     eventPtr = GetEvent(iPtr, argc==3 ? "-1" : argv[3]);
  505.     if (eventPtr == NULL) {
  506.         return TCL_ERROR;
  507.     }
  508.     words = GetWords(iPtr, eventPtr->command, argv[2]);
  509.     if (words == NULL) {
  510.         return TCL_ERROR;
  511.     }
  512.     RevResult(iPtr, words);
  513.     iPtr->result = words;
  514.     iPtr->freeProc = (Tcl_FreeProc *) free;
  515.     return TCL_OK;
  516.     }
  517.  
  518.     Tcl_AppendResult(interp, "bad option \"", argv[1],
  519.         "\": must be add, change, event, info, keep, nextid, ",
  520.         "redo, substitute, or words", (char *) NULL);
  521.     return TCL_ERROR;
  522. }
  523.  
  524. /*
  525.  *----------------------------------------------------------------------
  526.  *
  527.  * MakeSpace --
  528.  *
  529.  *    Given a history event, make sure it has enough space for
  530.  *    a string of a given length (enlarge the string area if
  531.  *    necessary).
  532.  *
  533.  * Results:
  534.  *    None.
  535.  *
  536.  * Side effects:
  537.  *    More memory may get allocated.
  538.  *
  539.  *----------------------------------------------------------------------
  540.  */
  541.  
  542. static void
  543. MakeSpace(hPtr, size)
  544.     HistoryEvent *hPtr;
  545.     int size;            /* # of bytes needed in hPtr. */
  546. {
  547.     if (hPtr->bytesAvl < size) {
  548.     ckfree(hPtr->command);
  549.     hPtr->command = (char *) ckalloc((unsigned) size);
  550.     hPtr->bytesAvl = size;
  551.     }
  552. }
  553.  
  554. /*
  555.  *----------------------------------------------------------------------
  556.  *
  557.  * InsertRev --
  558.  *
  559.  *    Add a new revision to the list of those pending for iPtr.
  560.  *    Do it in a way that keeps the revision list sorted in
  561.  *    increasing order of firstIndex.  Also, eliminate revisions
  562.  *    that are subsets of other revisions.
  563.  *
  564.  * Results:
  565.  *    None.
  566.  *
  567.  * Side effects:
  568.  *    RevPtr is added to iPtr's revision list.
  569.  *
  570.  *----------------------------------------------------------------------
  571.  */
  572.  
  573. static void
  574. InsertRev(iPtr, revPtr)
  575.     Interp *iPtr;            /* Interpreter to use. */
  576.     register HistoryRev *revPtr;    /* Revision to add to iPtr's list. */
  577. {
  578.     register HistoryRev *curPtr;
  579.     register HistoryRev *prevPtr;
  580.  
  581.     for (curPtr = iPtr->revPtr, prevPtr = NULL; curPtr != NULL;
  582.         prevPtr = curPtr, curPtr = curPtr->nextPtr) {
  583.     /*
  584.      * If this revision includes the new one (or vice versa) then
  585.      * just eliminate the one that is a subset of the other.
  586.      */
  587.  
  588.     if ((revPtr->firstIndex <= curPtr->firstIndex)
  589.         && (revPtr->lastIndex >= curPtr->firstIndex)) {
  590.         curPtr->firstIndex = revPtr->firstIndex;
  591.         curPtr->lastIndex = revPtr->lastIndex;
  592.         curPtr->newSize = revPtr->newSize;
  593.         ckfree(curPtr->newBytes);
  594.         curPtr->newBytes = revPtr->newBytes;
  595.         ckfree((char *) revPtr);
  596.         return;
  597.     }
  598.     if ((revPtr->firstIndex >= curPtr->firstIndex)
  599.         && (revPtr->lastIndex <= curPtr->lastIndex)) {
  600.         ckfree(revPtr->newBytes);
  601.         ckfree((char *) revPtr);
  602.         return;
  603.     }
  604.  
  605.     if (revPtr->firstIndex < curPtr->firstIndex) {
  606.         break;
  607.     }
  608.     }
  609.  
  610.     /*
  611.      * Insert revPtr just after prevPtr.
  612.      */
  613.  
  614.     if (prevPtr == NULL) {
  615.     revPtr->nextPtr = iPtr->revPtr;
  616.     iPtr->revPtr = revPtr;
  617.     } else {
  618.     revPtr->nextPtr = prevPtr->nextPtr;
  619.     prevPtr->nextPtr = revPtr;
  620.     }
  621. }
  622.  
  623. /*
  624.  *----------------------------------------------------------------------
  625.  *
  626.  * RevCommand --
  627.  *
  628.  *    This procedure is invoked by the "history" command to record
  629.  *    a command revision.  See the comments at the beginning of the
  630.  *    file for more information about revisions.
  631.  *
  632.  * Results:
  633.  *    None.
  634.  *
  635.  * Side effects:
  636.  *    Revision information is recorded.
  637.  *
  638.  *----------------------------------------------------------------------
  639.  */
  640.  
  641. static void
  642. RevCommand(iPtr, string)
  643.     register Interp *iPtr;    /* Interpreter in which to perform the
  644.                  * substitution. */
  645.     char *string;        /* String to substitute. */
  646. {
  647.     register HistoryRev *revPtr;
  648.  
  649.     if ((iPtr->evalFirst == NULL) || (iPtr->revDisables > 0)) {
  650.     return;
  651.     }
  652.     revPtr = (HistoryRev *) ckalloc(sizeof(HistoryRev));
  653.     revPtr->firstIndex = iPtr->evalFirst - iPtr->historyFirst;
  654.     revPtr->lastIndex = iPtr->evalLast - iPtr->historyFirst;
  655.     revPtr->newSize = strlen(string);
  656.     revPtr->newBytes = (char *) ckalloc((unsigned) (revPtr->newSize+1));
  657.     strcpy(revPtr->newBytes, string);
  658.     InsertRev(iPtr, revPtr);
  659. }
  660.  
  661. /*
  662.  *----------------------------------------------------------------------
  663.  *
  664.  * RevResult --
  665.  *
  666.  *    This procedure is invoked by the "history" command to record
  667.  *    a result revision.  See the comments at the beginning of the
  668.  *    file for more information about revisions.
  669.  *
  670.  * Results:
  671.  *    None.
  672.  *
  673.  * Side effects:
  674.  *    Revision information is recorded.
  675.  *
  676.  *----------------------------------------------------------------------
  677.  */
  678.  
  679. static void
  680. RevResult(iPtr, string)
  681.     register Interp *iPtr;    /* Interpreter in which to perform the
  682.                  * substitution. */
  683.     char *string;        /* String to substitute. */
  684. {
  685.     register HistoryRev *revPtr;
  686.     char *evalFirst, *evalLast;
  687.     char *argv[2];
  688.  
  689.     if ((iPtr->evalFirst == NULL) || (iPtr->revDisables > 0)) {
  690.     return;
  691.     }
  692.  
  693.     /*
  694.      * Expand the replacement range to include the brackets that surround
  695.      * the command.  If there aren't any brackets (i.e. this command was
  696.      * invoked at top-level) then don't do any revision.  Also, if there
  697.      * are several commands in brackets, of which this is just one,
  698.      * then don't do any revision.
  699.      */
  700.  
  701.     evalFirst = iPtr->evalFirst;
  702.     evalLast = iPtr->evalLast + 1;
  703.     while (1) {
  704.     if (evalFirst == iPtr->historyFirst) {
  705.         return;
  706.     }
  707.     evalFirst--;
  708.     if (*evalFirst == '[') {
  709.         break;
  710.     }
  711.     if (!isspace(UCHAR(*evalFirst))) {
  712.         return;
  713.     }
  714.     }
  715.     if (*evalLast != ']') {
  716.     return;
  717.     }
  718.  
  719.     revPtr = (HistoryRev *) ckalloc(sizeof(HistoryRev));
  720.     revPtr->firstIndex = evalFirst - iPtr->historyFirst;
  721.     revPtr->lastIndex = evalLast - iPtr->historyFirst;
  722.     argv[0] = string;
  723.     revPtr->newBytes = Tcl_Merge(1, argv);
  724.     revPtr->newSize = strlen(revPtr->newBytes);
  725.     InsertRev(iPtr, revPtr);
  726. }
  727.  
  728. /*
  729.  *----------------------------------------------------------------------
  730.  *
  731.  * DoRevs --
  732.  *
  733.  *    This procedure is called to apply the history revisions that
  734.  *    have been recorded in iPtr.
  735.  *
  736.  * Results:
  737.  *    None.
  738.  *
  739.  * Side effects:
  740.  *    The most recent entry in the history for iPtr may be modified.
  741.  *
  742.  *----------------------------------------------------------------------
  743.  */
  744.  
  745. static void
  746. DoRevs(iPtr)
  747.     register Interp *iPtr;    /* Interpreter whose history is to
  748.                  * be modified. */
  749. {
  750.     register HistoryRev *revPtr;
  751.     register HistoryEvent *eventPtr;
  752.     char *newCommand, *p;
  753.     unsigned int size;
  754.     int bytesSeen, count;
  755.  
  756.     if (iPtr->revPtr == NULL) {
  757.     return;
  758.     }
  759.  
  760.     /*
  761.      * The revision is done in two passes.  The first pass computes the
  762.      * amount of space needed for the revised event, and the second pass
  763.      * pieces together the new event and frees up the revisions.
  764.      */
  765.  
  766.     eventPtr = &iPtr->events[iPtr->curEvent];
  767.     size = strlen(eventPtr->command) + 1;
  768.     for (revPtr = iPtr->revPtr; revPtr != NULL; revPtr = revPtr->nextPtr) {
  769.     size -= revPtr->lastIndex + 1 - revPtr->firstIndex;
  770.     size += revPtr->newSize;
  771.     }
  772.  
  773.     newCommand = (char *) ckalloc(size);
  774.     p = newCommand;
  775.     bytesSeen = 0;
  776.     for (revPtr = iPtr->revPtr; revPtr != NULL; ) {
  777.     HistoryRev *nextPtr = revPtr->nextPtr;
  778.  
  779.     count = revPtr->firstIndex - bytesSeen;
  780.     if (count > 0) {
  781.         strncpy(p, eventPtr->command + bytesSeen, count);
  782.         p += count;
  783.     }
  784.     strncpy(p, revPtr->newBytes, revPtr->newSize);
  785.     p += revPtr->newSize;
  786.     bytesSeen = revPtr->lastIndex+1;
  787.     ckfree(revPtr->newBytes);
  788.     ckfree((char *) revPtr);
  789.     revPtr = nextPtr;
  790.     }
  791.     if (&p[strlen(&eventPtr->command[bytesSeen]) + 1] >
  792.         &newCommand[size]) {
  793.     printf("Assertion failed!\n");
  794.     }
  795.     strcpy(p, eventPtr->command + bytesSeen);
  796.  
  797.     /*
  798.      * Replace the command in the event.
  799.      */
  800.  
  801.     ckfree(eventPtr->command);
  802.     eventPtr->command = newCommand;
  803.     eventPtr->bytesAvl = size;
  804.     iPtr->revPtr = NULL;
  805. }
  806.  
  807. /*
  808.  *----------------------------------------------------------------------
  809.  *
  810.  * GetEvent --
  811.  *
  812.  *    Given a textual description of an event (see the manual page
  813.  *    for legal values) find the corresponding event and return its
  814.  *    command string.
  815.  *
  816.  * Results:
  817.  *    The return value is a pointer to the event named by "string".
  818.  *    If no such event exists, then NULL is returned and an error
  819.  *    message is left in iPtr.
  820.  *
  821.  * Side effects:
  822.  *    None.
  823.  *
  824.  *----------------------------------------------------------------------
  825.  */
  826.  
  827. static HistoryEvent *
  828. GetEvent(iPtr, string)
  829.     register Interp *iPtr;    /* Interpreter in which to look. */
  830.     char *string;        /* Description of event. */
  831. {
  832.     int eventNum, index;
  833.     register HistoryEvent *eventPtr;
  834.     int length;
  835.  
  836.     /*
  837.      * First check for a numeric specification of an event.
  838.      */
  839.  
  840.     if (isdigit(UCHAR(*string)) || (*string == '-')) {
  841.     if (Tcl_GetInt((Tcl_Interp *) iPtr, string, &eventNum) != TCL_OK) {
  842.         return NULL;
  843.     }
  844.     if (eventNum < 0) {
  845.         eventNum += iPtr->curEventNum;
  846.         }
  847.     if (eventNum > iPtr->curEventNum) {
  848.         Tcl_AppendResult((Tcl_Interp *) iPtr, "event \"", string,
  849.             "\" hasn't occurred yet", (char *) NULL);
  850.         return NULL;
  851.     }
  852.     if ((eventNum <= iPtr->curEventNum-iPtr->numEvents)
  853.         || (eventNum <= 0)) {
  854.         Tcl_AppendResult((Tcl_Interp *) iPtr, "event \"", string,
  855.             "\" is too far in the past", (char *) NULL);
  856.         return NULL;
  857.     }
  858.     index = iPtr->curEvent + (eventNum - iPtr->curEventNum);
  859.     if (index < 0) {
  860.         index += iPtr->numEvents;
  861.     }
  862.     return &iPtr->events[index];
  863.     }
  864.  
  865.     /*
  866.      * Next, check for an event that contains the string as a prefix or
  867.      * that matches the string in the sense of Tcl_StringMatch.
  868.      */
  869.  
  870.     length = strlen(string);
  871.     for (index = iPtr->curEvent - 1; ; index--) {
  872.     if (index < 0) {
  873.         index += iPtr->numEvents;
  874.     }
  875.     if (index == iPtr->curEvent) {
  876.         break;
  877.     }
  878.     eventPtr = &iPtr->events[index];
  879.     if ((strncmp(eventPtr->command, string, length) == 0)
  880.         || Tcl_StringMatch(eventPtr->command, string)) {
  881.         return eventPtr;
  882.     }
  883.     }
  884.  
  885.     Tcl_AppendResult((Tcl_Interp *) iPtr, "no event matches \"", string,
  886.         "\"", (char *) NULL);
  887.     return NULL;
  888. }
  889.  
  890. /*
  891.  *----------------------------------------------------------------------
  892.  *
  893.  * SubsAndEval --
  894.  *
  895.  *    Generate a new command by making a textual substitution in
  896.  *    the "cmd" argument.  Then execute the new command.
  897.  *
  898.  * Results:
  899.  *    The return value is a standard Tcl error.
  900.  *
  901.  * Side effects:
  902.  *    History gets revised if the substitution is occurring on
  903.  *    a recorded command line.  Also, the re-executed command
  904.  *    may produce side-effects.
  905.  *
  906.  *----------------------------------------------------------------------
  907.  */
  908.  
  909. static int
  910. SubsAndEval(iPtr, cmd, old, new)
  911.     register Interp *iPtr;    /* Interpreter in which to execute
  912.                  * new command. */
  913.     char *cmd;            /* Command in which to substitute. */
  914.     char *old;            /* String to search for in command. */
  915.     char *new;            /* Replacement string for "old". */
  916. {
  917.     char *src, *dst, *newCmd;
  918.     int count, oldLength, newLength, length, result;
  919.  
  920.     /*
  921.      * Figure out how much space it will take to hold the
  922.      * substituted command (and complain if the old string
  923.      * doesn't appear in the original command).
  924.      */
  925.  
  926.     oldLength = strlen(old);
  927.     newLength = strlen(new);
  928.     src = cmd;
  929.     count = 0;
  930.     while (1) {
  931.     src = strstr(src, old);
  932.     if (src == NULL) {
  933.         break;
  934.     }
  935.     src += oldLength;
  936.     count++;
  937.     }
  938.     if (count == 0) {
  939.     Tcl_AppendResult((Tcl_Interp *) iPtr, "\"", old,
  940.         "\" doesn't appear in event", (char *) NULL);
  941.     return TCL_ERROR;
  942.     }
  943.     length = strlen(cmd) + count*(newLength - oldLength);
  944.  
  945.     /*
  946.      * Generate a substituted command.
  947.      */
  948.  
  949.     newCmd = (char *) ckalloc((unsigned) (length + 1));
  950.     dst = newCmd;
  951.     while (1) {
  952.     src = strstr(cmd, old);
  953.     if (src == NULL) {
  954.         strcpy(dst, cmd);
  955.         break;
  956.     }
  957.     strncpy(dst, cmd, src-cmd);
  958.     dst += src-cmd;
  959.     strcpy(dst, new);
  960.     dst += newLength;
  961.     cmd = src + oldLength;
  962.     }
  963.  
  964.     RevCommand(iPtr, newCmd);
  965.     result = Tcl_Eval((Tcl_Interp *) iPtr, newCmd);
  966.     ckfree(newCmd);
  967.     return result;
  968. }
  969.  
  970. /*
  971.  *----------------------------------------------------------------------
  972.  *
  973.  * GetWords --
  974.  *
  975.  *    Given a command string, return one or more words from the
  976.  *    command string.
  977.  *
  978.  * Results:
  979.  *    The return value is a pointer to a dynamically-allocated
  980.  *    string containing the words of command specified by "words".
  981.  *    If the word specifier has improper syntax then an error
  982.  *    message is placed in iPtr->result and NULL is returned.
  983.  *
  984.  * Side effects:
  985.  *    Memory is allocated.  It is the caller's responsibilty to
  986.  *    free the returned string..
  987.  *
  988.  *----------------------------------------------------------------------
  989.  */
  990.  
  991. static char *
  992. GetWords(iPtr, command, words)
  993.     register Interp *iPtr;    /* Tcl interpreter in which to place
  994.                  * an error message if needed. */
  995.     char *command;        /* Command string. */
  996.     char *words;        /* Description of which words to extract
  997.                  * from the command.  Either num[-num] or
  998.                  * a pattern. */
  999. {
  1000.     char *result;
  1001.     char *start, *end, *dst;
  1002.     register char *next;
  1003.     int first;            /* First word desired. -1 means last word
  1004.                  * only. */
  1005.     int last;            /* Last word desired.  -1 means use everything
  1006.                  * up to the end. */
  1007.     int index;            /* Index of current word. */
  1008.     char *pattern;
  1009.  
  1010.     /*
  1011.      * Figure out whether we're looking for a numerical range or for
  1012.      * a pattern.
  1013.      */
  1014.  
  1015.     pattern = NULL;
  1016.     first = 0;
  1017.     last = -1;
  1018.     if (*words == '$') {
  1019.     if (words[1] != '\0') {
  1020.         goto error;
  1021.     }
  1022.     first = -1;
  1023.     } else if (isdigit(UCHAR(*words))) {
  1024.     first = strtoul(words, &start, 0);
  1025.     if (*start == 0) {
  1026.         last = first;
  1027.     } else if (*start == '-') {
  1028.         start++;
  1029.         if (*start == '$') {
  1030.         start++;
  1031.         } else if (isdigit(UCHAR(*start))) {
  1032.         last = strtoul(start, &start, 0);
  1033.         } else {
  1034.         goto error;
  1035.         }
  1036.         if (*start != 0) {
  1037.         goto error;
  1038.         }
  1039.     }
  1040.     if ((first > last) && (last != -1)) {
  1041.         goto error;
  1042.     }
  1043.     } else {
  1044.     pattern = words;
  1045.     }
  1046.  
  1047.     /*
  1048.      * Scan through the words one at a time, copying those that are
  1049.      * relevant into the result string.  Allocate a result area large
  1050.      * enough to hold all the words if necessary.
  1051.      */
  1052.  
  1053.     result = (char *) ckalloc((unsigned) (strlen(command) + 1));
  1054.     dst = result;
  1055.     for (next = command; isspace(UCHAR(*next)); next++) {
  1056.     /* Empty loop body:  just find start of first word. */
  1057.     }
  1058.     for (index = 0; *next != 0; index++) {
  1059.     start = next;
  1060.     end = TclWordEnd(next, 0, (int *) NULL);
  1061.     if (*end != 0) {
  1062.         end++;
  1063.         for (next = end; isspace(UCHAR(*next)); next++) {
  1064.         /* Empty loop body:  just find start of next word. */
  1065.         }
  1066.     }
  1067.     if ((first > index) || ((first == -1) && (*next != 0))) {
  1068.         continue;
  1069.     }
  1070.     if ((last != -1) && (last < index)) {
  1071.         continue;
  1072.     }
  1073.     if (pattern != NULL) {
  1074.         int match;
  1075.         char savedChar = *end;
  1076.  
  1077.         *end = 0;
  1078.         match = Tcl_StringMatch(start, pattern);
  1079.         *end = savedChar;
  1080.         if (!match) {
  1081.         continue;
  1082.         }
  1083.     }
  1084.     if (dst != result) {
  1085.         *dst = ' ';
  1086.         dst++;
  1087.     }
  1088.     strncpy(dst, start, (end-start));
  1089.     dst += end-start;
  1090.     }
  1091.     *dst = 0;
  1092.  
  1093.     /*
  1094.      * Check for an out-of-range argument index.
  1095.      */
  1096.  
  1097.     if ((last >= index) || (first >= index)) {
  1098.     ckfree(result);
  1099.     Tcl_AppendResult((Tcl_Interp *) iPtr, "word selector \"", words,
  1100.         "\" specified non-existent words", (char *) NULL);
  1101.     return NULL;
  1102.     }
  1103.     return result;
  1104.  
  1105.     error:
  1106.     Tcl_AppendResult((Tcl_Interp *) iPtr, "bad word selector \"", words,
  1107.         "\":  should be num-num or pattern", (char *) NULL);
  1108.     return NULL;
  1109. }
  1110.