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