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