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