Annotation of micropolis/src/tcl/tclhist.c, revision 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-1991 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: /user6/ouster/tcl/RCS/tclHistory.c,v 1.23 91/10/28 09:11:16 ouster Exp $ SPRITE (Berkeley)";
        !            21: #endif /* not lint */
        !            22: 
        !            23: #include "tclint.h"
        !            24: 
        !            25: /*
        !            26:  * This history stuff is mostly straightforward, except for one thing
        !            27:  * that makes everything very complicated.  Suppose that the following
        !            28:  * commands get executed:
        !            29:  *     echo foo
        !            30:  *     history redo
        !            31:  * It's important that the history event recorded for the second command
        !            32:  * be "echo foo", not "history redo".  Otherwise, if another "history redo"
        !            33:  * command is typed, it will result in infinite recursions on the
        !            34:  * "history redo" command.  Thus, the actual recorded history must be
        !            35:  *     echo foo
        !            36:  *     echo foo
        !            37:  * To do this, the history command revises recorded history as part of
        !            38:  * its execution.  In the example above, when "history redo" starts
        !            39:  * execution, the current event is "history redo", but the history
        !            40:  * command arranges for the current event to be changed to "echo foo".
        !            41:  *
        !            42:  * There are three additional complications.  The first is that history
        !            43:  * substitution may only be part of a command, as in the following
        !            44:  * command sequence:
        !            45:  *     echo foo bar
        !            46:  *     echo [history word 3]
        !            47:  * In this case, the second event should be recorded as "echo bar".  Only
        !            48:  * part of the recorded event is to be modified.  Fortunately, Tcl_Eval
        !            49:  * helps with this by recording (in the evalFirst and evalLast fields of
        !            50:  * the intepreter) the location of the command being executed, so the
        !            51:  * history module can replace exactly the range of bytes corresponding
        !            52:  * to the history substitution command.
        !            53:  *
        !            54:  * The second complication is that there are two ways to revise history:
        !            55:  * replace a command, and replace the result of a command.  Consider the
        !            56:  * two examples below:
        !            57:  *     format {result is %d} $num         |    format {result is %d} $num
        !            58:  *     print [history redo]               |    print [history word 3]
        !            59:  * Recorded history for these two cases should be as follows:
        !            60:  *     format {result is %d} $num         |    format {result is %d} $num
        !            61:  *     print [format {result is %d} $num] |    print $num
        !            62:  * In the left case, the history command was replaced with another command
        !            63:  * to be executed (the brackets were retained), but in the case on the
        !            64:  * right the result of executing the history command was replaced (i.e.
        !            65:  * brackets were replaced too).
        !            66:  *
        !            67:  * The third complication is that there could potentially be many
        !            68:  * history substitutions within a single command, as in:
        !            69:  *     echo [history word 3] [history word 2]
        !            70:  * There could even be nested history substitutions, as in:
        !            71:  *     history subs abc [history word 2]
        !            72:  * If history revisions were made immediately during each "history" command
        !            73:  * invocations, it would be very difficult to produce the correct cumulative
        !            74:  * effect from several substitutions in the same command.  To get around
        !            75:  * this problem, the actual history revision isn't made during the execution
        !            76:  * of the "history" command.  Information about the changes is just recorded,
        !            77:  * in xxx records, and the actual changes are made during the next call to
        !            78:  * Tcl_RecordHistory (when we know that execution of the previous command
        !            79:  * has finished).
        !            80:  */
        !            81: 
        !            82: /*
        !            83:  * Default space allocation for command strings:
        !            84:  */
        !            85: 
        !            86: #define INITIAL_CMD_SIZE 40
        !            87: 
        !            88: /*
        !            89:  * Forward declarations for procedures defined later in this file:
        !            90:  */
        !            91: 
        !            92: static void            DoRevs _ANSI_ARGS_((Interp *iPtr));
        !            93: static HistoryEvent *  GetEvent _ANSI_ARGS_((Interp *iPtr, char *string));
        !            94: static char *          GetWords _ANSI_ARGS_((Interp *iPtr, char *command,
        !            95:                            char *words));
        !            96: static void            InsertRev _ANSI_ARGS_((Interp *iPtr,
        !            97:                            HistoryRev *revPtr));
        !            98: static void            MakeSpace _ANSI_ARGS_((HistoryEvent *hPtr, int size));
        !            99: static void            RevCommand _ANSI_ARGS_((Interp *iPtr, char *string));
        !           100: static void            RevResult _ANSI_ARGS_((Interp *iPtr, char *string));
        !           101: static int             SubsAndEval _ANSI_ARGS_((Interp *iPtr, char *cmd,
        !           102:                            char *old, char *new));
        !           103: 
        !           104: /*
        !           105:  *----------------------------------------------------------------------
        !           106:  *
        !           107:  * Tcl_InitHistory --
        !           108:  *
        !           109:  *     Initialize history-related state in an interpreter.
        !           110:  *
        !           111:  * Results:
        !           112:  *     None.
        !           113:  *
        !           114:  * Side effects:
        !           115:  *     History info is initialized in iPtr.
        !           116:  *
        !           117:  *----------------------------------------------------------------------
        !           118:  */
        !           119: 
        !           120: void
        !           121: Tcl_InitHistory(interp)
        !           122:     Tcl_Interp *interp;                /* Interpreter to initialize. */
        !           123: {
        !           124:     register Interp *iPtr = (Interp *) interp;
        !           125:     int i;
        !           126: 
        !           127:     if (iPtr->numEvents != 0) {
        !           128:        return;
        !           129:     }
        !           130:     iPtr->numEvents = 20;
        !           131:     iPtr->events = (HistoryEvent *)
        !           132:            ckalloc((unsigned) (iPtr->numEvents * sizeof(HistoryEvent)));
        !           133:     for (i = 0; i < iPtr->numEvents; i++) {
        !           134:        iPtr->events[i].command = (char *) ckalloc(INITIAL_CMD_SIZE);
        !           135:        *iPtr->events[i].command = 0;
        !           136:        iPtr->events[i].bytesAvl = INITIAL_CMD_SIZE;
        !           137:     }
        !           138:     iPtr->curEvent = 0;
        !           139:     iPtr->curEventNum = 0;
        !           140:     Tcl_CreateCommand((Tcl_Interp *) iPtr, "history", Tcl_HistoryCmd,
        !           141:            (ClientData) NULL, (void (*)()) NULL);
        !           142: }
        !           143: 
        !           144: /*
        !           145:  *----------------------------------------------------------------------
        !           146:  *
        !           147:  * Tcl_RecordAndEval --
        !           148:  *
        !           149:  *     This procedure adds its command argument to the current list of
        !           150:  *     recorded events and then executes the command by calling Tcl_Eval.
        !           151:  *
        !           152:  * Results:
        !           153:  *     The return value is a standard Tcl return value, the result of
        !           154:  *     executing cmd.
        !           155:  *
        !           156:  * Side effects:
        !           157:  *     The command is recorded and executed.  In addition, pending history
        !           158:  *     revisions are carried out, and information is set up to enable
        !           159:  *     Tcl_Eval to identify history command ranges.  This procedure also
        !           160:  *     initializes history information for the interpreter, if it hasn't
        !           161:  *     already been initialized.
        !           162:  *
        !           163:  *----------------------------------------------------------------------
        !           164:  */
        !           165: 
        !           166: int
        !           167: Tcl_RecordAndEval(interp, cmd, flags)
        !           168:     Tcl_Interp *interp;                /* Token for interpreter in which command
        !           169:                                 * will be executed. */
        !           170:     char *cmd;                 /* Command to record. */
        !           171:     int flags;                 /* Additional flags to pass to Tcl_Eval. 
        !           172:                                 * TCL_NO_EVAL means only record: don't
        !           173:                                 * execute command. */
        !           174: {
        !           175:     register Interp *iPtr = (Interp *) interp;
        !           176:     register HistoryEvent *eventPtr;
        !           177:     int length, result;
        !           178: 
        !           179:     if (iPtr->numEvents == 0) {
        !           180:        Tcl_InitHistory(interp);
        !           181:     }
        !           182:     DoRevs(iPtr);
        !           183: 
        !           184:     /*
        !           185:      * Don't record empty commands.
        !           186:      */
        !           187: 
        !           188:     while (isspace(*cmd)) {
        !           189:        cmd++;
        !           190:     }
        !           191:     if (*cmd == '\0') {
        !           192:        Tcl_ResetResult(interp);
        !           193:        return TCL_OK;
        !           194:     }
        !           195: 
        !           196:     iPtr->curEventNum++;
        !           197:     iPtr->curEvent++;
        !           198:     if (iPtr->curEvent >= iPtr->numEvents) {
        !           199:        iPtr->curEvent = 0;
        !           200:     }
        !           201:     eventPtr = &iPtr->events[iPtr->curEvent];
        !           202: 
        !           203:     /*
        !           204:      * Chop off trailing newlines before recording the command.
        !           205:      */
        !           206: 
        !           207:     length = strlen(cmd);
        !           208:     while (cmd[length-1] == '\n') {
        !           209:        length--;
        !           210:     }
        !           211:     MakeSpace(eventPtr, length + 1);
        !           212:     strncpy(eventPtr->command, cmd, length);
        !           213:     eventPtr->command[length] = 0;
        !           214: 
        !           215:     /*
        !           216:      * Execute the command.  Note: history revision isn't possible after
        !           217:      * a nested call to this procedure, because the event at the top of
        !           218:      * the history list no longer corresponds to what's going on when
        !           219:      * a nested call here returns.  Thus, must leave history revision
        !           220:      * disabled when we return.
        !           221:      */
        !           222: 
        !           223:     result = TCL_OK;
        !           224:     if (flags != TCL_NO_EVAL) {
        !           225:        iPtr->historyFirst = cmd;
        !           226:        iPtr->revDisables = 0;
        !           227:        result = Tcl_Eval(interp, cmd, flags | TCL_RECORD_BOUNDS,
        !           228:                (char **) NULL);
        !           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:     int length;
        !           262:     char c;
        !           263: 
        !           264:     /*
        !           265:      * If no arguments, treat the same as "history info".
        !           266:      */
        !           267: 
        !           268:     if (argc == 1) {
        !           269:        goto infoCmd;
        !           270:     }
        !           271: 
        !           272:     c = argv[1][0];
        !           273:     length = strlen(argv[1]);
        !           274: 
        !           275:     if ((c == 'a') && (strncmp(argv[1], "add", length)) == 0) {
        !           276:        if ((argc != 3) && (argc != 4)) {
        !           277:            Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
        !           278:                    " add event ?exec?\"", (char *) NULL);
        !           279:            return TCL_ERROR;
        !           280:        }
        !           281:        if (argc == 4) {
        !           282:            if (strncmp(argv[3], "exec", strlen(argv[3])) != 0) {
        !           283:                Tcl_AppendResult(interp, "bad argument \"", argv[3],
        !           284:                        "\": should be \"exec\"", (char *) NULL);
        !           285:                return TCL_ERROR;
        !           286:            }
        !           287:            return Tcl_RecordAndEval(interp, argv[2], 0);
        !           288:        }
        !           289:        return Tcl_RecordAndEval(interp, argv[2], TCL_NO_EVAL);
        !           290:     } else if ((c == 'c') && (strncmp(argv[1], "change", length)) == 0) {
        !           291:        if ((argc != 3) && (argc != 4)) {
        !           292:            Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
        !           293:                    " change newValue ?event?\"", (char *) NULL);
        !           294:            return TCL_ERROR;
        !           295:        }
        !           296:        if (argc == 3) {
        !           297:            eventPtr = &iPtr->events[iPtr->curEvent];
        !           298:            iPtr->revDisables += 1;
        !           299:            while (iPtr->revPtr != NULL) {
        !           300:                HistoryRev *nextPtr;
        !           301: 
        !           302:                ckfree(iPtr->revPtr->newBytes);
        !           303:                nextPtr = iPtr->revPtr->nextPtr;
        !           304:                ckfree((char *) iPtr->revPtr);
        !           305:                iPtr->revPtr = nextPtr;
        !           306:            }
        !           307:        } else {
        !           308:            eventPtr = GetEvent(iPtr, argv[3]);
        !           309:            if (eventPtr == NULL) {
        !           310:                return TCL_ERROR;
        !           311:            }
        !           312:        }
        !           313:        MakeSpace(eventPtr, strlen(argv[2]) + 1);
        !           314:        strcpy(eventPtr->command, argv[2]);
        !           315:        return TCL_OK;
        !           316:     } else if ((c == 'e') && (strncmp(argv[1], "event", length)) == 0) {
        !           317:        if (argc > 3) {
        !           318:            Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
        !           319:                    " event ?event?\"", (char *) NULL);
        !           320:            return TCL_ERROR;
        !           321:        }
        !           322:        eventPtr = GetEvent(iPtr, argc==2 ? "-1" : argv[2]);
        !           323:        if (eventPtr == NULL) {
        !           324:            return TCL_ERROR;
        !           325:        }
        !           326:        RevResult(iPtr, eventPtr->command);
        !           327:        Tcl_SetResult(interp, eventPtr->command, TCL_VOLATILE);
        !           328:        return TCL_OK;
        !           329:     } else if ((c == 'i') && (strncmp(argv[1], "info", length)) == 0) {
        !           330:        int count, indx, i;
        !           331:        char *newline;
        !           332: 
        !           333:        if ((argc != 2) && (argc != 3)) {
        !           334:            Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
        !           335:                    " info ?count?\"", (char *) NULL);
        !           336:            return TCL_ERROR;
        !           337:        }
        !           338:        infoCmd:
        !           339:        if (argc == 3) {
        !           340:            if (Tcl_GetInt(interp, argv[2], &count) != TCL_OK) {
        !           341:                return TCL_ERROR;
        !           342:            }
        !           343:            if (count > iPtr->numEvents) {
        !           344:                count = iPtr->numEvents;
        !           345:            }
        !           346:        } else {
        !           347:            count = iPtr->numEvents;
        !           348:        }
        !           349:        newline = "";
        !           350:        for (i = 0, indx = iPtr->curEvent + 1 + iPtr->numEvents - count;
        !           351:                i < count; i++, indx++) {
        !           352:            char *cur, *next, savedChar;
        !           353:            char serial[20];
        !           354: 
        !           355:            if (indx >= iPtr->numEvents) {
        !           356:                indx -= iPtr->numEvents;
        !           357:            }
        !           358:            cur = iPtr->events[indx].command;
        !           359:            if (*cur == '\0') {
        !           360:                continue;               /* No command recorded here. */
        !           361:            }
        !           362:            sprintf(serial, "%6d  ", iPtr->curEventNum + 1 - (count - i));
        !           363:            Tcl_AppendResult(interp, newline, serial, (char *) NULL);
        !           364:            newline = "\n";
        !           365: 
        !           366:            /*
        !           367:             * Tricky formatting here:  for multi-line commands, indent
        !           368:             * the continuation lines.
        !           369:             */
        !           370: 
        !           371:            while (1) {
        !           372:                next = strchr(cur, '\n');
        !           373:                if (next == NULL) {
        !           374:                    break;
        !           375:                }
        !           376:                next++;
        !           377:                savedChar = *next;
        !           378:                *next = 0;
        !           379:                Tcl_AppendResult(interp, cur, "\t", (char *) NULL);
        !           380:                *next = savedChar;
        !           381:                cur = next;
        !           382:            }
        !           383:            Tcl_AppendResult(interp, cur, (char *) NULL);
        !           384:        }
        !           385:        return TCL_OK;
        !           386:     } else if ((c == 'k') && (strncmp(argv[1], "keep", length)) == 0) {
        !           387:        int count, i, src;
        !           388:        HistoryEvent *events;
        !           389: 
        !           390:        if (argc != 3) {
        !           391:            Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
        !           392:                    " keep number\"", (char *) NULL);
        !           393:            return TCL_ERROR;
        !           394:        }
        !           395:        if (Tcl_GetInt(interp, argv[2], &count) != TCL_OK) {
        !           396:            return TCL_ERROR;
        !           397:        }
        !           398:        if ((count <= 0) || (count > 1000)) {
        !           399:            Tcl_AppendResult(interp, "illegal keep count \"", argv[2],
        !           400:                    "\"", (char *) NULL);
        !           401:            return TCL_ERROR;
        !           402:        }
        !           403: 
        !           404:        /*
        !           405:         * Create a new history array and copy as much existing history
        !           406:         * as possible from the old array.
        !           407:         */
        !           408: 
        !           409:        events = (HistoryEvent *)
        !           410:                ckalloc((unsigned) (count * sizeof(HistoryEvent)));
        !           411:        if (count < iPtr->numEvents) {
        !           412:            src = iPtr->curEvent + 1 - count;
        !           413:            if (src < 0) {
        !           414:                src += iPtr->numEvents;
        !           415:            }
        !           416:        } else {
        !           417:            src = iPtr->curEvent + 1;
        !           418:        }
        !           419:        for (i = 0; i < count; i++, src++) {
        !           420:            if (src >= iPtr->numEvents) {
        !           421:                src = 0;
        !           422:            }
        !           423:            if (i < iPtr->numEvents) {
        !           424:                events[i] = iPtr->events[src];
        !           425:                iPtr->events[src].command = NULL;
        !           426:            } else {
        !           427:                events[i].command = (char *) ckalloc(INITIAL_CMD_SIZE);
        !           428:                events[i].command[0] = 0;
        !           429:                events[i].bytesAvl = INITIAL_CMD_SIZE;
        !           430:            }
        !           431:        }
        !           432: 
        !           433:        /*
        !           434:         * Throw away everything left in the old history array, and
        !           435:         * substitute the new one for the old one.
        !           436:         */
        !           437: 
        !           438:        for (i = 0; i < iPtr->numEvents; i++) {
        !           439:            if (iPtr->events[i].command != NULL) {
        !           440:                ckfree(iPtr->events[i].command);
        !           441:            }
        !           442:        }
        !           443:        ckfree((char *) iPtr->events);
        !           444:        iPtr->events = events;
        !           445:        if (count < iPtr->numEvents) {
        !           446:            iPtr->curEvent = count-1;
        !           447:        } else {
        !           448:            iPtr->curEvent = iPtr->numEvents-1;
        !           449:        }
        !           450:        iPtr->numEvents = count;
        !           451:        return TCL_OK;
        !           452:     } else if ((c == 'n') && (strncmp(argv[1], "nextid", length)) == 0) {
        !           453:        if (argc != 2) {
        !           454:            Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
        !           455:                    " nextid\"", (char *) NULL);
        !           456:            return TCL_ERROR;
        !           457:        }
        !           458:        sprintf(iPtr->result, "%d", iPtr->curEventNum+1);
        !           459:        return TCL_OK;
        !           460:     } else if ((c == 'r') && (strncmp(argv[1], "redo", length)) == 0) {
        !           461:        if (argc > 3) {
        !           462:            Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
        !           463:                    " redo ?event?\"", (char *) NULL);
        !           464:            return TCL_ERROR;
        !           465:        }
        !           466:        eventPtr = GetEvent(iPtr, argc==2 ? "-1" : argv[2]);
        !           467:        if (eventPtr == NULL) {
        !           468:            return TCL_ERROR;
        !           469:        }
        !           470:        RevCommand(iPtr, eventPtr->command);
        !           471:        return Tcl_Eval(interp, eventPtr->command, 0, (char **) NULL);
        !           472:     } else if ((c == 's') && (strncmp(argv[1], "substitute", length)) == 0) {
        !           473:        if ((argc > 5) || (argc < 4)) {
        !           474:            Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
        !           475:                    " substitute old new ?event?\"", (char *) NULL);
        !           476:            return TCL_ERROR;
        !           477:        }
        !           478:        eventPtr = GetEvent(iPtr, argc==4 ? "-1" : argv[4]);
        !           479:        if (eventPtr == NULL) {
        !           480:            return TCL_ERROR;
        !           481:        }
        !           482:        return SubsAndEval(iPtr, eventPtr->command, argv[2], argv[3]);
        !           483:     } else if ((c == 'w') && (strncmp(argv[1], "words", length)) == 0) {
        !           484:        char *words;
        !           485: 
        !           486:        if ((argc != 3) && (argc != 4)) {
        !           487:            Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
        !           488:                    " words num-num/pat ?event?\"", (char *) NULL);
        !           489:            return TCL_ERROR;
        !           490:        }
        !           491:        eventPtr = GetEvent(iPtr, argc==3 ? "-1" : argv[3]);
        !           492:        if (eventPtr == NULL) {
        !           493:            return TCL_ERROR;
        !           494:        }
        !           495:        words = GetWords(iPtr, eventPtr->command, argv[2]);
        !           496:        if (words == NULL) {
        !           497:            return TCL_ERROR;
        !           498:        }
        !           499:        RevResult(iPtr, words);
        !           500:        iPtr->result = words;
        !           501:        iPtr->freeProc = (Tcl_FreeProc *) free;
        !           502:        return TCL_OK;
        !           503:     }
        !           504: 
        !           505:     Tcl_AppendResult(interp, "bad option \"", argv[1],
        !           506:            "\": must be add, change, event, info, keep, nextid, ",
        !           507:            "redo, substitute, or words", (char *) NULL);
        !           508:     return TCL_ERROR;
        !           509: }
        !           510: 
        !           511: /*
        !           512:  *----------------------------------------------------------------------
        !           513:  *
        !           514:  * MakeSpace --
        !           515:  *
        !           516:  *     Given a history event, make sure it has enough space for
        !           517:  *     a string of a given length (enlarge the string area if
        !           518:  *     necessary).
        !           519:  *
        !           520:  * Results:
        !           521:  *     None.
        !           522:  *
        !           523:  * Side effects:
        !           524:  *     More memory may get allocated.
        !           525:  *
        !           526:  *----------------------------------------------------------------------
        !           527:  */
        !           528: 
        !           529: static void
        !           530: MakeSpace(hPtr, size)
        !           531:     HistoryEvent *hPtr;
        !           532:     int size;                  /* # of bytes needed in hPtr. */
        !           533: {
        !           534:     if (hPtr->bytesAvl < size) {
        !           535:        ckfree(hPtr->command);
        !           536:        hPtr->command = (char *) ckalloc((unsigned) size);
        !           537:        hPtr->bytesAvl = size;
        !           538:     }
        !           539: }
        !           540: 
        !           541: /*
        !           542:  *----------------------------------------------------------------------
        !           543:  *
        !           544:  * InsertRev --
        !           545:  *
        !           546:  *     Add a new revision to the list of those pending for iPtr.
        !           547:  *     Do it in a way that keeps the revision list sorted in
        !           548:  *     increasing order of firstIndex.  Also, eliminate revisions
        !           549:  *     that are subsets of other revisions.
        !           550:  *
        !           551:  * Results:
        !           552:  *     None.
        !           553:  *
        !           554:  * Side effects:
        !           555:  *     RevPtr is added to iPtr's revision list.
        !           556:  *
        !           557:  *----------------------------------------------------------------------
        !           558:  */
        !           559: 
        !           560: static void
        !           561: InsertRev(iPtr, revPtr)
        !           562:     Interp *iPtr;                      /* Interpreter to use. */
        !           563:     register HistoryRev *revPtr;       /* Revision to add to iPtr's list. */
        !           564: {
        !           565:     register HistoryRev *curPtr;
        !           566:     register HistoryRev *prevPtr;
        !           567: 
        !           568:     for (curPtr = iPtr->revPtr, prevPtr = NULL; curPtr != NULL;
        !           569:            prevPtr = curPtr, curPtr = curPtr->nextPtr) {
        !           570:        /*
        !           571:         * If this revision includes the new one (or vice versa) then
        !           572:         * just eliminate the one that is a subset of the other.
        !           573:         */
        !           574: 
        !           575:        if ((revPtr->firstIndex <= curPtr->firstIndex)
        !           576:                && (revPtr->lastIndex >= curPtr->firstIndex)) {
        !           577:            curPtr->firstIndex = revPtr->firstIndex;
        !           578:            curPtr->lastIndex = revPtr->lastIndex;
        !           579:            curPtr->newSize = revPtr->newSize;
        !           580:            ckfree(curPtr->newBytes);
        !           581:            curPtr->newBytes = revPtr->newBytes;
        !           582:            ckfree((char *) revPtr);
        !           583:            return;
        !           584:        }
        !           585:        if ((revPtr->firstIndex >= curPtr->firstIndex)
        !           586:                && (revPtr->lastIndex <= curPtr->lastIndex)) {
        !           587:            ckfree(revPtr->newBytes);
        !           588:            ckfree((char *) revPtr);
        !           589:            return;
        !           590:        }
        !           591: 
        !           592:        if (revPtr->firstIndex < curPtr->firstIndex) {
        !           593:            break;
        !           594:        }
        !           595:     }
        !           596: 
        !           597:     /*
        !           598:      * Insert revPtr just after prevPtr.
        !           599:      */
        !           600: 
        !           601:     if (prevPtr == NULL) {
        !           602:        revPtr->nextPtr = iPtr->revPtr;
        !           603:        iPtr->revPtr = revPtr;
        !           604:     } else {
        !           605:        revPtr->nextPtr = prevPtr->nextPtr;
        !           606:        prevPtr->nextPtr = revPtr;
        !           607:     }
        !           608: }
        !           609: 
        !           610: /*
        !           611:  *----------------------------------------------------------------------
        !           612:  *
        !           613:  * RevCommand --
        !           614:  *
        !           615:  *     This procedure is invoked by the "history" command to record
        !           616:  *     a command revision.  See the comments at the beginning of the
        !           617:  *     file for more information about revisions.
        !           618:  *
        !           619:  * Results:
        !           620:  *     None.
        !           621:  *
        !           622:  * Side effects:
        !           623:  *     Revision information is recorded.
        !           624:  *
        !           625:  *----------------------------------------------------------------------
        !           626:  */
        !           627: 
        !           628: static void
        !           629: RevCommand(iPtr, string)
        !           630:     register Interp *iPtr;     /* Interpreter in which to perform the
        !           631:                                 * substitution. */
        !           632:     char *string;              /* String to substitute. */
        !           633: {
        !           634:     register HistoryRev *revPtr;
        !           635: 
        !           636:     if ((iPtr->evalFirst == NULL) || (iPtr->revDisables > 0)) {
        !           637:        return;
        !           638:     }
        !           639:     revPtr = (HistoryRev *) ckalloc(sizeof(HistoryRev));
        !           640:     revPtr->firstIndex = iPtr->evalFirst - iPtr->historyFirst;
        !           641:     revPtr->lastIndex = iPtr->evalLast - iPtr->historyFirst;
        !           642:     revPtr->newSize = strlen(string);
        !           643:     revPtr->newBytes = (char *) ckalloc((unsigned) (revPtr->newSize+1));
        !           644:     strcpy(revPtr->newBytes, string);
        !           645:     InsertRev(iPtr, revPtr);
        !           646: }
        !           647: 
        !           648: /*
        !           649:  *----------------------------------------------------------------------
        !           650:  *
        !           651:  * RevResult --
        !           652:  *
        !           653:  *     This procedure is invoked by the "history" command to record
        !           654:  *     a result revision.  See the comments at the beginning of the
        !           655:  *     file for more information about revisions.
        !           656:  *
        !           657:  * Results:
        !           658:  *     None.
        !           659:  *
        !           660:  * Side effects:
        !           661:  *     Revision information is recorded.
        !           662:  *
        !           663:  *----------------------------------------------------------------------
        !           664:  */
        !           665: 
        !           666: static void
        !           667: RevResult(iPtr, string)
        !           668:     register Interp *iPtr;     /* Interpreter in which to perform the
        !           669:                                 * substitution. */
        !           670:     char *string;              /* String to substitute. */
        !           671: {
        !           672:     register HistoryRev *revPtr;
        !           673:     char *evalFirst, *evalLast;
        !           674:     char *argv[2];
        !           675: 
        !           676:     if ((iPtr->evalFirst == NULL) || (iPtr->revDisables > 0)) {
        !           677:        return;
        !           678:     }
        !           679: 
        !           680:     /*
        !           681:      * Expand the replacement range to include the brackets that surround
        !           682:      * the command.  If there aren't any brackets (i.e. this command was
        !           683:      * invoked at top-level) then don't do any revision.  Also, if there
        !           684:      * are several commands in brackets, of which this is just one,
        !           685:      * then don't do any revision.
        !           686:      */
        !           687: 
        !           688:     evalFirst = iPtr->evalFirst;
        !           689:     evalLast = iPtr->evalLast + 1;
        !           690:     while (1) {
        !           691:        if (evalFirst == iPtr->historyFirst) {
        !           692:            return;
        !           693:        }
        !           694:        evalFirst--;
        !           695:        if (*evalFirst == '[') {
        !           696:            break;
        !           697:        }
        !           698:        if (!isspace(*evalFirst)) {
        !           699:            return;
        !           700:        }
        !           701:     }
        !           702:     if (*evalLast != ']') {
        !           703:        return;
        !           704:     }
        !           705: 
        !           706:     revPtr = (HistoryRev *) ckalloc(sizeof(HistoryRev));
        !           707:     revPtr->firstIndex = evalFirst - iPtr->historyFirst;
        !           708:     revPtr->lastIndex = evalLast - iPtr->historyFirst;
        !           709:     argv[0] = string;
        !           710:     revPtr->newBytes = Tcl_Merge(1, argv);
        !           711:     revPtr->newSize = strlen(revPtr->newBytes);
        !           712:     InsertRev(iPtr, revPtr);
        !           713: }
        !           714: 
        !           715: /*
        !           716:  *----------------------------------------------------------------------
        !           717:  *
        !           718:  * DoRevs --
        !           719:  *
        !           720:  *     This procedure is called to apply the history revisions that
        !           721:  *     have been recorded in iPtr.
        !           722:  *
        !           723:  * Results:
        !           724:  *     None.
        !           725:  *
        !           726:  * Side effects:
        !           727:  *     The most recent entry in the history for iPtr may be modified.
        !           728:  *
        !           729:  *----------------------------------------------------------------------
        !           730:  */
        !           731: 
        !           732: static void
        !           733: DoRevs(iPtr)
        !           734:     register Interp *iPtr;     /* Interpreter whose history is to
        !           735:                                 * be modified. */
        !           736: {
        !           737:     register HistoryRev *revPtr;
        !           738:     register HistoryEvent *eventPtr;
        !           739:     char *newCommand, *p;
        !           740:     unsigned int size;
        !           741:     int bytesSeen, count;
        !           742: 
        !           743:     if (iPtr->revPtr == NULL) {
        !           744:        return;
        !           745:     }
        !           746: 
        !           747:     /*
        !           748:      * The revision is done in two passes.  The first pass computes the
        !           749:      * amount of space needed for the revised event, and the second pass
        !           750:      * pieces together the new event and frees up the revisions.
        !           751:      */
        !           752: 
        !           753:     eventPtr = &iPtr->events[iPtr->curEvent];
        !           754:     size = strlen(eventPtr->command) + 1;
        !           755:     for (revPtr = iPtr->revPtr; revPtr != NULL; revPtr = revPtr->nextPtr) {
        !           756:        size -= revPtr->lastIndex + 1 - revPtr->firstIndex;
        !           757:        size += revPtr->newSize;
        !           758:     }
        !           759: 
        !           760:     newCommand = (char *) ckalloc(size);
        !           761:     p = newCommand;
        !           762:     bytesSeen = 0;
        !           763:     for (revPtr = iPtr->revPtr; revPtr != NULL; ) {
        !           764:        HistoryRev *nextPtr = revPtr->nextPtr;
        !           765: 
        !           766:        count = revPtr->firstIndex - bytesSeen;
        !           767:        if (count > 0) {
        !           768:            strncpy(p, eventPtr->command + bytesSeen, count);
        !           769:            p += count;
        !           770:        }
        !           771:        strncpy(p, revPtr->newBytes, revPtr->newSize);
        !           772:        p += revPtr->newSize;
        !           773:        bytesSeen = revPtr->lastIndex+1;
        !           774:        ckfree(revPtr->newBytes);
        !           775:        ckfree((char *) revPtr);
        !           776:        revPtr = nextPtr;
        !           777:     }
        !           778:     if (&p[strlen(&eventPtr->command[bytesSeen]) + 1] >
        !           779:            &newCommand[size]) {
        !           780:        printf("Assertion failed!\n");
        !           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(*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, 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, 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, 0, (char **) NULL);
        !           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(*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(*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(*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);
        !          1048:        for (next = end; isspace(*next); next++) {
        !          1049:            /* Empty loop body:  just find start of next word. */
        !          1050:        }
        !          1051:        if ((first > index) || ((first == -1) && (*next != 0))) {
        !          1052:            continue;
        !          1053:        }
        !          1054:        if ((last != -1) && (last < index)) {
        !          1055:            continue;
        !          1056:        }
        !          1057:        if (pattern != NULL) {
        !          1058:            int match;
        !          1059:            char savedChar = *end;
        !          1060: 
        !          1061:            *end = 0;
        !          1062:            match = Tcl_StringMatch(start, pattern);
        !          1063:            *end = savedChar;
        !          1064:            if (!match) {
        !          1065:                continue;
        !          1066:            }
        !          1067:        }
        !          1068:        if (dst != result) {
        !          1069:            *dst = ' ';
        !          1070:            dst++;
        !          1071:        }
        !          1072:        strncpy(dst, start, (end-start));
        !          1073:        dst += end-start;
        !          1074:     }
        !          1075:     *dst = 0;
        !          1076: 
        !          1077:     /*
        !          1078:      * Check for an out-of-range argument index.
        !          1079:      */
        !          1080: 
        !          1081:     if ((last >= index) || (first >= index)) {
        !          1082:        ckfree(result);
        !          1083:        Tcl_AppendResult((Tcl_Interp *) iPtr, "word selector \"", words,
        !          1084:                "\" specified non-existent words", (char *) NULL);
        !          1085:        return NULL;
        !          1086:     }
        !          1087:     return result;
        !          1088: 
        !          1089:     error:
        !          1090:     Tcl_AppendResult((Tcl_Interp *) iPtr, "bad word selector \"", words,
        !          1091:            "\":  should be num-num or pattern", (char *) NULL);
        !          1092:     return NULL;
        !          1093: }

unix.superglobalmegacorp.com

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