Annotation of researchv10dc/cmd/worm/scsi/tcl/tclHistory.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 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.