/* * tclHistory.c -- * * This module implements history as an optional addition to Tcl. * It can be called to record commands ("events") before they are * executed, and it provides a command that may be used to perform * history substitutions. * * Copyright (c) 1990-1993 The Regents of the University of California. * Copyright (c) 1994-1995 Sun Microsystems, Inc. * * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. * * SCCS: @(#) tclHistory.c 1.40 96/02/15 11:50:24 */ #include "tclInt.h" #include "tclPort.h" /* * This history stuff is mostly straightforward, except for one thing * that makes everything very complicated. Suppose that the following * commands get executed: * echo foo * history redo * It's important that the history event recorded for the second command * be "echo foo", not "history redo". Otherwise, if another "history redo" * command is typed, it will result in infinite recursions on the * "history redo" command. Thus, the actual recorded history must be * echo foo * echo foo * To do this, the history command revises recorded history as part of * its execution. In the example above, when "history redo" starts * execution, the current event is "history redo", but the history * command arranges for the current event to be changed to "echo foo". * * There are three additional complications. The first is that history * substitution may only be part of a command, as in the following * command sequence: * echo foo bar * echo [history word 3] * In this case, the second event should be recorded as "echo bar". Only * part of the recorded event is to be modified. Fortunately, Tcl_Eval * helps with this by recording (in the evalFirst and evalLast fields of * the intepreter) the location of the command being executed, so the * history module can replace exactly the range of bytes corresponding * to the history substitution command. * * The second complication is that there are two ways to revise history: * replace a command, and replace the result of a command. Consider the * two examples below: * format {result is %d} $num | format {result is %d} $num * print [history redo] | print [history word 3] * Recorded history for these two cases should be as follows: * format {result is %d} $num | format {result is %d} $num * print [format {result is %d} $num] | print $num * In the left case, the history command was replaced with another command * to be executed (the brackets were retained), but in the case on the * right the result of executing the history command was replaced (i.e. * brackets were replaced too). * * The third complication is that there could potentially be many * history substitutions within a single command, as in: * echo [history word 3] [history word 2] * There could even be nested history substitutions, as in: * history subs abc [history word 2] * If history revisions were made immediately during each "history" command * invocations, it would be very difficult to produce the correct cumulative * effect from several substitutions in the same command. To get around * this problem, the actual history revision isn't made during the execution * of the "history" command. Information about the changes is just recorded, * in xxx records, and the actual changes are made during the next call to * Tcl_RecordHistory (when we know that execution of the previous command * has finished). */ /* * Default space allocation for command strings: */ #define INITIAL_CMD_SIZE 40 /* * Forward declarations for procedures defined later in this file: */ static void DoRevs _ANSI_ARGS_((Interp *iPtr)); static HistoryEvent * GetEvent _ANSI_ARGS_((Interp *iPtr, char *string)); static char * GetWords _ANSI_ARGS_((Interp *iPtr, char *command, char *words)); static void InitHistory _ANSI_ARGS_((Interp *iPtr)); static void InsertRev _ANSI_ARGS_((Interp *iPtr, HistoryRev *revPtr)); static void MakeSpace _ANSI_ARGS_((HistoryEvent *hPtr, int size)); static void RevCommand _ANSI_ARGS_((Interp *iPtr, char *string)); static void RevResult _ANSI_ARGS_((Interp *iPtr, char *string)); static int SubsAndEval _ANSI_ARGS_((Interp *iPtr, char *cmd, char *old, char *new)); /* *---------------------------------------------------------------------- * * InitHistory -- * * Initialize history-related state in an interpreter. * * Results: * None. * * Side effects: * History info is initialized in iPtr. * *---------------------------------------------------------------------- */ static void InitHistory(iPtr) register Interp *iPtr; /* Interpreter to initialize. */ { int i; if (iPtr->numEvents != 0) { return; } iPtr->numEvents = 20; iPtr->events = (HistoryEvent *) ckalloc((unsigned) (iPtr->numEvents * sizeof(HistoryEvent))); for (i = 0; i < iPtr->numEvents; i++) { iPtr->events[i].command = (char *) ckalloc(INITIAL_CMD_SIZE); *iPtr->events[i].command = 0; iPtr->events[i].bytesAvl = INITIAL_CMD_SIZE; } iPtr->curEvent = 0; iPtr->curEventNum = 0; } /* *---------------------------------------------------------------------- * * Tcl_RecordAndEval -- * * This procedure adds its command argument to the current list of * recorded events and then executes the command by calling * Tcl_Eval. * * Results: * The return value is a standard Tcl return value, the result of * executing cmd. * * Side effects: * The command is recorded and executed. In addition, pending history * revisions are carried out, and information is set up to enable * Tcl_Eval to identify history command ranges. This procedure also * initializes history information for the interpreter, if it hasn't * already been initialized. * *---------------------------------------------------------------------- */ int Tcl_RecordAndEval(interp, cmd, flags) Tcl_Interp *interp; /* Token for interpreter in which command * will be executed. */ char *cmd; /* Command to record. */ int flags; /* Additional flags. TCL_NO_EVAL means * only record: don't execute command. * TCL_EVAL_GLOBAL means use Tcl_GlobalEval * instead of Tcl_Eval. */ { register Interp *iPtr = (Interp *) interp; register HistoryEvent *eventPtr; int length, result; if (iPtr->numEvents == 0) { InitHistory(iPtr); } DoRevs(iPtr); /* * Don't record empty commands. */ while (isspace(UCHAR(*cmd))) { cmd++; } if (*cmd == '\0') { Tcl_ResetResult(interp); return TCL_OK; } iPtr->curEventNum++; iPtr->curEvent++; if (iPtr->curEvent >= iPtr->numEvents) { iPtr->curEvent = 0; } eventPtr = &iPtr->events[iPtr->curEvent]; /* * Chop off trailing newlines before recording the command. */ length = strlen(cmd); while (cmd[length-1] == '\n') { length--; } MakeSpace(eventPtr, length + 1); strncpy(eventPtr->command, cmd, (size_t) length); eventPtr->command[length] = 0; /* * Execute the command. Note: history revision isn't possible after * a nested call to this procedure, because the event at the top of * the history list no longer corresponds to what's going on when * a nested call here returns. Thus, must leave history revision * disabled when we return. */ result = TCL_OK; if (!(flags & TCL_NO_EVAL)) { iPtr->historyFirst = cmd; iPtr->revDisables = 0; iPtr->evalFlags = (flags & ~TCL_EVAL_GLOBAL) | TCL_RECORD_BOUNDS; if (flags & TCL_EVAL_GLOBAL) { result = Tcl_GlobalEval(interp, cmd); } else { result = Tcl_Eval(interp, cmd); } } iPtr->revDisables = 1; return result; } /* *---------------------------------------------------------------------- * * Tcl_HistoryCmd -- * * This procedure is invoked to process the "history" Tcl command. * See the user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ /* ARGSUSED */ int Tcl_HistoryCmd(dummy, interp, argc, argv) ClientData dummy; /* Not used. */ Tcl_Interp *interp; /* Current interpreter. */ int argc; /* Number of arguments. */ char **argv; /* Argument strings. */ { register Interp *iPtr = (Interp *) interp; register HistoryEvent *eventPtr; size_t length; int c; if (iPtr->numEvents == 0) { InitHistory(iPtr); } /* * If no arguments, treat the same as "history info". */ if (argc == 1) { goto infoCmd; } c = argv[1][0]; length = strlen(argv[1]); if ((c == 'a') && (strncmp(argv[1], "add", length)) == 0) { if ((argc != 3) && (argc != 4)) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " add event ?exec?\"", (char *) NULL); return TCL_ERROR; } if (argc == 4) { if (strncmp(argv[3], "exec", strlen(argv[3])) != 0) { Tcl_AppendResult(interp, "bad argument \"", argv[3], "\": should be \"exec\"", (char *) NULL); return TCL_ERROR; } return Tcl_RecordAndEval(interp, argv[2], 0); } return Tcl_RecordAndEval(interp, argv[2], TCL_NO_EVAL); } else if ((c == 'c') && (strncmp(argv[1], "change", length)) == 0) { if ((argc != 3) && (argc != 4)) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " change newValue ?event?\"", (char *) NULL); return TCL_ERROR; } if (argc == 3) { eventPtr = &iPtr->events[iPtr->curEvent]; iPtr->revDisables += 1; while (iPtr->revPtr != NULL) { HistoryRev *nextPtr; ckfree(iPtr->revPtr->newBytes); nextPtr = iPtr->revPtr->nextPtr; ckfree((char *) iPtr->revPtr); iPtr->revPtr = nextPtr; } } else { eventPtr = GetEvent(iPtr, argv[3]); if (eventPtr == NULL) { return TCL_ERROR; } } MakeSpace(eventPtr, (int) strlen(argv[2]) + 1); strcpy(eventPtr->command, argv[2]); return TCL_OK; } else if ((c == 'e') && (strncmp(argv[1], "event", length)) == 0) { if (argc > 3) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " event ?event?\"", (char *) NULL); return TCL_ERROR; } eventPtr = GetEvent(iPtr, argc==2 ? "-1" : argv[2]); if (eventPtr == NULL) { return TCL_ERROR; } RevResult(iPtr, eventPtr->command); Tcl_SetResult(interp, eventPtr->command, TCL_VOLATILE); return TCL_OK; } else if ((c == 'i') && (strncmp(argv[1], "info", length)) == 0) { int count, indx, i; char *newline; if ((argc != 2) && (argc != 3)) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " info ?count?\"", (char *) NULL); return TCL_ERROR; } infoCmd: if (argc == 3) { if (Tcl_GetInt(interp, argv[2], &count) != TCL_OK) { return TCL_ERROR; } if (count > iPtr->numEvents) { count = iPtr->numEvents; } } else { count = iPtr->numEvents; } newline = ""; for (i = 0, indx = iPtr->curEvent + 1 + iPtr->numEvents - count; i < count; i++, indx++) { char *cur, *next, savedChar; char serial[20]; if (indx >= iPtr->numEvents) { indx -= iPtr->numEvents; } cur = iPtr->events[indx].command; if (*cur == '\0') { continue; /* No command recorded here. */ } sprintf(serial, "%6d ", iPtr->curEventNum + 1 - (count - i)); Tcl_AppendResult(interp, newline, serial, (char *) NULL); newline = "\n"; /* * Tricky formatting here: for multi-line commands, indent * the continuation lines. */ while (1) { next = strchr(cur, '\n'); if (next == NULL) { break; } next++; savedChar = *next; *next = 0; Tcl_AppendResult(interp, cur, "\t", (char *) NULL); *next = savedChar; cur = next; } Tcl_AppendResult(interp, cur, (char *) NULL); } return TCL_OK; } else if ((c == 'k') && (strncmp(argv[1], "keep", length)) == 0) { int count, i, src; HistoryEvent *events; if (argc != 3) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " keep number\"", (char *) NULL); return TCL_ERROR; } if (Tcl_GetInt(interp, argv[2], &count) != TCL_OK) { return TCL_ERROR; } if ((count <= 0) || (count > 1000)) { Tcl_AppendResult(interp, "illegal keep count \"", argv[2], "\"", (char *) NULL); return TCL_ERROR; } /* * Create a new history array and copy as much existing history * as possible from the old array. */ events = (HistoryEvent *) ckalloc((unsigned) (count * sizeof(HistoryEvent))); if (count < iPtr->numEvents) { src = iPtr->curEvent + 1 - count; if (src < 0) { src += iPtr->numEvents; } } else { src = iPtr->curEvent + 1; } for (i = 0; i < count; i++, src++) { if (src >= iPtr->numEvents) { src = 0; } if (i < iPtr->numEvents) { events[i] = iPtr->events[src]; iPtr->events[src].command = NULL; } else { events[i].command = (char *) ckalloc(INITIAL_CMD_SIZE); events[i].command[0] = 0; events[i].bytesAvl = INITIAL_CMD_SIZE; } } /* * Throw away everything left in the old history array, and * substitute the new one for the old one. */ for (i = 0; i < iPtr->numEvents; i++) { if (iPtr->events[i].command != NULL) { ckfree(iPtr->events[i].command); } } ckfree((char *) iPtr->events); iPtr->events = events; if (count < iPtr->numEvents) { iPtr->curEvent = count-1; } else { iPtr->curEvent = iPtr->numEvents-1; } iPtr->numEvents = count; return TCL_OK; } else if ((c == 'n') && (strncmp(argv[1], "nextid", length)) == 0) { if (argc != 2) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " nextid\"", (char *) NULL); return TCL_ERROR; } sprintf(iPtr->result, "%d", iPtr->curEventNum+1); return TCL_OK; } else if ((c == 'r') && (strncmp(argv[1], "redo", length)) == 0) { if (argc > 3) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " redo ?event?\"", (char *) NULL); return TCL_ERROR; } eventPtr = GetEvent(iPtr, argc==2 ? "-1" : argv[2]); if (eventPtr == NULL) { return TCL_ERROR; } RevCommand(iPtr, eventPtr->command); return Tcl_Eval(interp, eventPtr->command); } else if ((c == 's') && (strncmp(argv[1], "substitute", length)) == 0) { if ((argc > 5) || (argc < 4)) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " substitute old new ?event?\"", (char *) NULL); return TCL_ERROR; } eventPtr = GetEvent(iPtr, argc==4 ? "-1" : argv[4]); if (eventPtr == NULL) { return TCL_ERROR; } return SubsAndEval(iPtr, eventPtr->command, argv[2], argv[3]); } else if ((c == 'w') && (strncmp(argv[1], "words", length)) == 0) { char *words; if ((argc != 3) && (argc != 4)) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " words num-num/pat ?event?\"", (char *) NULL); return TCL_ERROR; } eventPtr = GetEvent(iPtr, argc==3 ? "-1" : argv[3]); if (eventPtr == NULL) { return TCL_ERROR; } words = GetWords(iPtr, eventPtr->command, argv[2]); if (words == NULL) { return TCL_ERROR; } RevResult(iPtr, words); iPtr->result = words; iPtr->freeProc = TCL_DYNAMIC; return TCL_OK; } Tcl_AppendResult(interp, "bad option \"", argv[1], "\": must be add, change, event, info, keep, nextid, ", "redo, substitute, or words", (char *) NULL); return TCL_ERROR; } /* *---------------------------------------------------------------------- * * MakeSpace -- * * Given a history event, make sure it has enough space for * a string of a given length (enlarge the string area if * necessary). * * Results: * None. * * Side effects: * More memory may get allocated. * *---------------------------------------------------------------------- */ static void MakeSpace(hPtr, size) HistoryEvent *hPtr; int size; /* # of bytes needed in hPtr. */ { if (hPtr->bytesAvl < size) { ckfree(hPtr->command); hPtr->command = (char *) ckalloc((unsigned) size); hPtr->bytesAvl = size; } } /* *---------------------------------------------------------------------- * * InsertRev -- * * Add a new revision to the list of those pending for iPtr. * Do it in a way that keeps the revision list sorted in * increasing order of firstIndex. Also, eliminate revisions * that are subsets of other revisions. * * Results: * None. * * Side effects: * RevPtr is added to iPtr's revision list. * *---------------------------------------------------------------------- */ static void InsertRev(iPtr, revPtr) Interp *iPtr; /* Interpreter to use. */ register HistoryRev *revPtr; /* Revision to add to iPtr's list. */ { register HistoryRev *curPtr; register HistoryRev *prevPtr; for (curPtr = iPtr->revPtr, prevPtr = NULL; curPtr != NULL; prevPtr = curPtr, curPtr = curPtr->nextPtr) { /* * If this revision includes the new one (or vice versa) then * just eliminate the one that is a subset of the other. */ if ((revPtr->firstIndex <= curPtr->firstIndex) && (revPtr->lastIndex >= curPtr->firstIndex)) { curPtr->firstIndex = revPtr->firstIndex; curPtr->lastIndex = revPtr->lastIndex; curPtr->newSize = revPtr->newSize; ckfree(curPtr->newBytes); curPtr->newBytes = revPtr->newBytes; ckfree((char *) revPtr); return; } if ((revPtr->firstIndex >= curPtr->firstIndex) && (revPtr->lastIndex <= curPtr->lastIndex)) { ckfree(revPtr->newBytes); ckfree((char *) revPtr); return; } if (revPtr->firstIndex < curPtr->firstIndex) { break; } } /* * Insert revPtr just after prevPtr. */ if (prevPtr == NULL) { revPtr->nextPtr = iPtr->revPtr; iPtr->revPtr = revPtr; } else { revPtr->nextPtr = prevPtr->nextPtr; prevPtr->nextPtr = revPtr; } } /* *---------------------------------------------------------------------- * * RevCommand -- * * This procedure is invoked by the "history" command to record * a command revision. See the comments at the beginning of the * file for more information about revisions. * * Results: * None. * * Side effects: * Revision information is recorded. * *---------------------------------------------------------------------- */ static void RevCommand(iPtr, string) register Interp *iPtr; /* Interpreter in which to perform the * substitution. */ char *string; /* String to substitute. */ { register HistoryRev *revPtr; if ((iPtr->evalFirst == NULL) || (iPtr->revDisables > 0)) { return; } revPtr = (HistoryRev *) ckalloc(sizeof(HistoryRev)); revPtr->firstIndex = iPtr->evalFirst - iPtr->historyFirst; revPtr->lastIndex = iPtr->evalLast - iPtr->historyFirst; revPtr->newSize = strlen(string); revPtr->newBytes = (char *) ckalloc((unsigned) (revPtr->newSize+1)); strcpy(revPtr->newBytes, string); InsertRev(iPtr, revPtr); } /* *---------------------------------------------------------------------- * * RevResult -- * * This procedure is invoked by the "history" command to record * a result revision. See the comments at the beginning of the * file for more information about revisions. * * Results: * None. * * Side effects: * Revision information is recorded. * *---------------------------------------------------------------------- */ static void RevResult(iPtr, string) register Interp *iPtr; /* Interpreter in which to perform the * substitution. */ char *string; /* String to substitute. */ { register HistoryRev *revPtr; char *evalFirst, *evalLast; char *argv[2]; if ((iPtr->evalFirst == NULL) || (iPtr->revDisables > 0)) { return; } /* * Expand the replacement range to include the brackets that surround * the command. If there aren't any brackets (i.e. this command was * invoked at top-level) then don't do any revision. Also, if there * are several commands in brackets, of which this is just one, * then don't do any revision. */ evalFirst = iPtr->evalFirst; evalLast = iPtr->evalLast + 1; while (1) { if (evalFirst == iPtr->historyFirst) { return; } evalFirst--; if (*evalFirst == '[') { break; } if (!isspace(UCHAR(*evalFirst))) { return; } } if (*evalLast != ']') { return; } revPtr = (HistoryRev *) ckalloc(sizeof(HistoryRev)); revPtr->firstIndex = evalFirst - iPtr->historyFirst; revPtr->lastIndex = evalLast - iPtr->historyFirst; argv[0] = string; revPtr->newBytes = Tcl_Merge(1, argv); revPtr->newSize = strlen(revPtr->newBytes); InsertRev(iPtr, revPtr); } /* *---------------------------------------------------------------------- * * DoRevs -- * * This procedure is called to apply the history revisions that * have been recorded in iPtr. * * Results: * None. * * Side effects: * The most recent entry in the history for iPtr may be modified. * *---------------------------------------------------------------------- */ static void DoRevs(iPtr) register Interp *iPtr; /* Interpreter whose history is to * be modified. */ { register HistoryRev *revPtr; register HistoryEvent *eventPtr; char *newCommand, *p; unsigned int size; int bytesSeen, count; if (iPtr->revPtr == NULL) { return; } /* * The revision is done in two passes. The first pass computes the * amount of space needed for the revised event, and the second pass * pieces together the new event and frees up the revisions. */ eventPtr = &iPtr->events[iPtr->curEvent]; size = strlen(eventPtr->command) + 1; for (revPtr = iPtr->revPtr; revPtr != NULL; revPtr = revPtr->nextPtr) { size -= revPtr->lastIndex + 1 - revPtr->firstIndex; size += revPtr->newSize; } newCommand = (char *) ckalloc(size); p = newCommand; bytesSeen = 0; for (revPtr = iPtr->revPtr; revPtr != NULL; ) { HistoryRev *nextPtr = revPtr->nextPtr; count = revPtr->firstIndex - bytesSeen; if (count > 0) { strncpy(p, eventPtr->command + bytesSeen, (size_t) count); p += count; } strncpy(p, revPtr->newBytes, (size_t) revPtr->newSize); p += revPtr->newSize; bytesSeen = revPtr->lastIndex+1; ckfree(revPtr->newBytes); ckfree((char *) revPtr); revPtr = nextPtr; } strcpy(p, eventPtr->command + bytesSeen); /* * Replace the command in the event. */ ckfree(eventPtr->command); eventPtr->command = newCommand; eventPtr->bytesAvl = size; iPtr->revPtr = NULL; } /* *---------------------------------------------------------------------- * * GetEvent -- * * Given a textual description of an event (see the manual page * for legal values) find the corresponding event and return its * command string. * * Results: * The return value is a pointer to the event named by "string". * If no such event exists, then NULL is returned and an error * message is left in iPtr. * * Side effects: * None. * *---------------------------------------------------------------------- */ static HistoryEvent * GetEvent(iPtr, string) register Interp *iPtr; /* Interpreter in which to look. */ char *string; /* Description of event. */ { int eventNum, index; register HistoryEvent *eventPtr; int length; /* * First check for a numeric specification of an event. */ if (isdigit(UCHAR(*string)) || (*string == '-')) { if (Tcl_GetInt((Tcl_Interp *) iPtr, string, &eventNum) != TCL_OK) { return NULL; } if (eventNum < 0) { eventNum += iPtr->curEventNum; } if (eventNum > iPtr->curEventNum) { Tcl_AppendResult((Tcl_Interp *) iPtr, "event \"", string, "\" hasn't occurred yet", (char *) NULL); return NULL; } if ((eventNum <= iPtr->curEventNum-iPtr->numEvents) || (eventNum <= 0)) { Tcl_AppendResult((Tcl_Interp *) iPtr, "event \"", string, "\" is too far in the past", (char *) NULL); return NULL; } index = iPtr->curEvent + (eventNum - iPtr->curEventNum); if (index < 0) { index += iPtr->numEvents; } return &iPtr->events[index]; } /* * Next, check for an event that contains the string as a prefix or * that matches the string in the sense of Tcl_StringMatch. */ length = strlen(string); for (index = iPtr->curEvent - 1; ; index--) { if (index < 0) { index += iPtr->numEvents; } if (index == iPtr->curEvent) { break; } eventPtr = &iPtr->events[index]; if ((strncmp(eventPtr->command, string, (size_t) length) == 0) || Tcl_StringMatch(eventPtr->command, string)) { return eventPtr; } } Tcl_AppendResult((Tcl_Interp *) iPtr, "no event matches \"", string, "\"", (char *) NULL); return NULL; } /* *---------------------------------------------------------------------- * * SubsAndEval -- * * Generate a new command by making a textual substitution in * the "cmd" argument. Then execute the new command. * * Results: * The return value is a standard Tcl error. * * Side effects: * History gets revised if the substitution is occurring on * a recorded command line. Also, the re-executed command * may produce side-effects. * *---------------------------------------------------------------------- */ static int SubsAndEval(iPtr, cmd, old, new) register Interp *iPtr; /* Interpreter in which to execute * new command. */ char *cmd; /* Command in which to substitute. */ char *old; /* String to search for in command. */ char *new; /* Replacement string for "old". */ { char *src, *dst, *newCmd; int count, oldLength, newLength, length, result; /* * Figure out how much space it will take to hold the * substituted command (and complain if the old string * doesn't appear in the original command). */ oldLength = strlen(old); newLength = strlen(new); src = cmd; count = 0; while (1) { src = strstr(src, old); if (src == NULL) { break; } src += oldLength; count++; } if (count == 0) { Tcl_AppendResult((Tcl_Interp *) iPtr, "\"", old, "\" doesn't appear in event", (char *) NULL); return TCL_ERROR; } length = strlen(cmd) + count*(newLength - oldLength); /* * Generate a substituted command. */ newCmd = (char *) ckalloc((unsigned) (length + 1)); dst = newCmd; while (1) { src = strstr(cmd, old); if (src == NULL) { strcpy(dst, cmd); break; } strncpy(dst, cmd, (size_t) (src-cmd)); dst += src-cmd; strcpy(dst, new); dst += newLength; cmd = src + oldLength; } RevCommand(iPtr, newCmd); result = Tcl_Eval((Tcl_Interp *) iPtr, newCmd); ckfree(newCmd); return result; } /* *---------------------------------------------------------------------- * * GetWords -- * * Given a command string, return one or more words from the * command string. * * Results: * The return value is a pointer to a dynamically-allocated * string containing the words of command specified by "words". * If the word specifier has improper syntax then an error * message is placed in iPtr->result and NULL is returned. * * Side effects: * Memory is allocated. It is the caller's responsibilty to * free the returned string.. * *---------------------------------------------------------------------- */ static char * GetWords(iPtr, command, words) register Interp *iPtr; /* Tcl interpreter in which to place * an error message if needed. */ char *command; /* Command string. */ char *words; /* Description of which words to extract * from the command. Either num[-num] or * a pattern. */ { char *result; char *start, *end, *dst; register char *next; int first; /* First word desired. -1 means last word * only. */ int last; /* Last word desired. -1 means use everything * up to the end. */ int index; /* Index of current word. */ char *pattern; /* * Figure out whether we're looking for a numerical range or for * a pattern. */ pattern = NULL; first = 0; last = -1; if (*words == '$') { if (words[1] != '\0') { goto error; } first = -1; } else if (isdigit(UCHAR(*words))) { first = strtoul(words, &start, 0); if (*start == 0) { last = first; } else if (*start == '-') { start++; if (*start == '$') { start++; } else if (isdigit(UCHAR(*start))) { last = strtoul(start, &start, 0); } else { goto error; } if (*start != 0) { goto error; } } if ((first > last) && (last != -1)) { goto error; } } else { pattern = words; } /* * Scan through the words one at a time, copying those that are * relevant into the result string. Allocate a result area large * enough to hold all the words if necessary. */ result = (char *) ckalloc((unsigned) (strlen(command) + 1)); dst = result; for (next = command; isspace(UCHAR(*next)); next++) { /* Empty loop body: just find start of first word. */ } for (index = 0; *next != 0; index++) { start = next; end = TclWordEnd(next, 0, (int *) NULL); if (*end != 0) { end++; for (next = end; isspace(UCHAR(*next)); next++) { /* Empty loop body: just find start of next word. */ } } if ((first > index) || ((first == -1) && (*next != 0))) { continue; } if ((last != -1) && (last < index)) { continue; } if (pattern != NULL) { int match; char savedChar = *end; *end = 0; match = Tcl_StringMatch(start, pattern); *end = savedChar; if (!match) { continue; } } if (dst != result) { *dst = ' '; dst++; } strncpy(dst, start, (size_t) (end-start)); dst += end-start; } *dst = 0; /* * Check for an out-of-range argument index. */ if ((last >= index) || (first >= index)) { ckfree(result); Tcl_AppendResult((Tcl_Interp *) iPtr, "word selector \"", words, "\" specified non-existent words", (char *) NULL); return NULL; } return result; error: Tcl_AppendResult((Tcl_Interp *) iPtr, "bad word selector \"", words, "\": should be num-num or pattern", (char *) NULL); return NULL; }