|
|
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: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.