|
|
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.