Annotation of researchv10no/cmd/worm/scsi/tcl/tclHistory.c, revision 1.1.1.1

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

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.