⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 tclhistory.c

📁 tcl源码详细资料
💻 C
📖 第 1 页 / 共 2 页
字号:
#ifndef EXCLUDE_TCL/*  * tclHistory.c -- * *	This module implements history as an optional addition to Tcl. *	It can be called to record commands ("events") before they are *	executed, and it provides a command that may be used to perform *	history substitutions. * * Copyright 1990-1991 Regents of the University of California * Permission to use, copy, modify, and distribute this * software and its documentation for any purpose and without * fee is hereby granted, provided that the above copyright * notice appear in all copies.  The University of California * makes no representations about the suitability of this * software for any purpose.  It is provided "as is" without * express or implied warranty. */#include "tclInt.h"/* * This history stuff is mostly straightforward, except for one thing * that makes everything very complicated.  Suppose that the following * commands get executed: *	echo foo *	history redo * It's important that the history event recorded for the second command * be "echo foo", not "history redo".  Otherwise, if another "history redo" * command is typed, it will result in infinite recursions on the * "history redo" command.  Thus, the actual recorded history must be *	echo foo *	echo foo * To do this, the history command revises recorded history as part of * its execution.  In the example above, when "history redo" starts * execution, the current event is "history redo", but the history * command arranges for the current event to be changed to "echo foo". * * There are three additional complications.  The first is that history * substitution may only be part of a command, as in the following * command sequence: *	echo foo bar *	echo [history word 3] * In this case, the second event should be recorded as "echo bar".  Only * part of the recorded event is to be modified.  Fortunately, Tcl_Eval * helps with this by recording (in the evalFirst and evalLast fields of * the intepreter) the location of the command being executed, so the * history module can replace exactly the range of bytes corresponding * to the history substitution command. * * The second complication is that there are two ways to revise history: * replace a command, and replace the result of a command.  Consider the * two examples below: *	format {result is %d} $num	   |	format {result is %d} $num *	print [history redo]		   |	print [history word 3] * Recorded history for these two cases should be as follows: *	format {result is %d} $num	   |	format {result is %d} $num *	print [format {result is %d} $num] |	print $num * In the left case, the history command was replaced with another command * to be executed (the brackets were retained), but in the case on the * right the result of executing the history command was replaced (i.e. * brackets were replaced too). * * The third complication is that there could potentially be many * history substitutions within a single command, as in: *	echo [history word 3] [history word 2] * There could even be nested history substitutions, as in: *	history subs abc [history word 2] * If history revisions were made immediately during each "history" command * invocations, it would be very difficult to produce the correct cumulative * effect from several substitutions in the same command.  To get around * this problem, the actual history revision isn't made during the execution * of the "history" command.  Information about the changes is just recorded, * in xxx records, and the actual changes are made during the next call to * Tcl_RecordHistory (when we know that execution of the previous command * has finished). *//* * Default space allocation for command strings: */#define INITIAL_CMD_SIZE 40/* * Forward declarations for procedures defined later in this file: */static void		DoRevs _ANSI_ARGS_((Interp *iPtr));static HistoryEvent *	GetEvent _ANSI_ARGS_((Interp *iPtr, char *string));static char *		GetWords _ANSI_ARGS_((Interp *iPtr, char *command,			    char *words));static void		InsertRev _ANSI_ARGS_((Interp *iPtr,			    HistoryRev *revPtr));static void		MakeSpace _ANSI_ARGS_((HistoryEvent *hPtr, int size));static void		RevCommand _ANSI_ARGS_((Interp *iPtr, char *string));static void		RevResult _ANSI_ARGS_((Interp *iPtr, char *string));static int		SubsAndEval _ANSI_ARGS_((Interp *iPtr, char *cmd,			    char *old, char *new));/* *---------------------------------------------------------------------- * * Tcl_InitHistory -- * *	Initialize history-related state in an interpreter. * * Results: *	None. * * Side effects: *	History info is initialized in iPtr. * *---------------------------------------------------------------------- */voidTcl_InitHistory(interp)    Tcl_Interp *interp;		/* Interpreter to initialize. */{    register Interp *iPtr = (Interp *) interp;    int i;    if (iPtr->numEvents != 0) {	return;    }    iPtr->numEvents = 20;    iPtr->events = (HistoryEvent *)	    ckalloc((unsigned) (iPtr->numEvents * sizeof(HistoryEvent)));    for (i = 0; i < iPtr->numEvents; i++) {	iPtr->events[i].command = (char *) ckalloc(INITIAL_CMD_SIZE);	*iPtr->events[i].command = 0;	iPtr->events[i].bytesAvl = INITIAL_CMD_SIZE;    }    iPtr->curEvent = 0;    iPtr->curEventNum = 0;    Tcl_CreateCommand((Tcl_Interp *) iPtr, "history", Tcl_HistoryCmd,	    (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);}/* *---------------------------------------------------------------------- * * Tcl_RecordAndEval -- * *	This procedure adds its command argument to the current list of *	recorded events and then executes the command by calling Tcl_Eval. * * Results: *	The return value is a standard Tcl return value, the result of *	executing cmd. * * Side effects: *	The command is recorded and executed.  In addition, pending history *	revisions are carried out, and information is set up to enable *	Tcl_Eval to identify history command ranges.  This procedure also *	initializes history information for the interpreter, if it hasn't *	already been initialized. * *---------------------------------------------------------------------- */intTcl_RecordAndEval(interp, cmd, flags)    Tcl_Interp *interp;		/* Token for interpreter in which command				 * will be executed. */    char *cmd;			/* Command to record. */    int flags;			/* Additional flags to pass to Tcl_Eval. 				 * TCL_NO_EVAL means only record: don't				 * execute command. */{    register Interp *iPtr = (Interp *) interp;    register HistoryEvent *eventPtr;    int length, result;    if (iPtr->numEvents == 0) {	Tcl_InitHistory(interp);    }    DoRevs(iPtr);    /*     * Don't record empty commands.     */    while (isspace(*cmd)) {	cmd++;    }    if (*cmd == '\0') {	Tcl_ResetResult(interp);	return TCL_OK;    }    iPtr->curEventNum++;    iPtr->curEvent++;    if (iPtr->curEvent >= iPtr->numEvents) {	iPtr->curEvent = 0;    }    eventPtr = &iPtr->events[iPtr->curEvent];    /*     * Chop off trailing newlines before recording the command.     */    length = strlen(cmd);    while (cmd[length-1] == '\n') {	length--;    }    MakeSpace(eventPtr, length + 1);    strncpy(eventPtr->command, cmd, length);    eventPtr->command[length] = 0;    /*     * Execute the command.  Note: history revision isn't possible after     * a nested call to this procedure, because the event at the top of     * the history list no longer corresponds to what's going on when     * a nested call here returns.  Thus, must leave history revision     * disabled when we return.     */    result = TCL_OK;    if (flags != TCL_NO_EVAL) {	iPtr->historyFirst = cmd;	iPtr->revDisables = 0;	result = Tcl_Eval(interp, cmd, flags | TCL_RECORD_BOUNDS,		(char **) NULL);    }    iPtr->revDisables = 1;    return result;}/* *---------------------------------------------------------------------- * * Tcl_HistoryCmd -- * *	This procedure is invoked to process the "history" Tcl command. *	See the user documentation for details on what it does. * * Results: *	A standard Tcl result. * * Side effects: *	See the user documentation. * *---------------------------------------------------------------------- */	/* ARGSUSED */intTcl_HistoryCmd(dummy, interp, argc, argv)    ClientData dummy;			/* Not used. */    Tcl_Interp *interp;			/* Current interpreter. */    int argc;				/* Number of arguments. */    char **argv;			/* Argument strings. */{    register Interp *iPtr = (Interp *) interp;    register HistoryEvent *eventPtr;    int length;    char c;    /*     * If no arguments, treat the same as "history info".     */    if (argc == 1) {	goto infoCmd;    }    c = argv[1][0];    length = strlen(argv[1]);    if ((c == 'a') && (strncmp(argv[1], "add", length)) == 0) {	if ((argc != 3) && (argc != 4)) {	    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],		    " add event ?exec?\"", (char *) NULL);	    return TCL_ERROR;	}	if (argc == 4) {	    if (strncmp(argv[3], "exec", strlen(argv[3])) != 0) {		Tcl_AppendResult(interp, "bad argument \"", argv[3],			"\": should be \"exec\"", (char *) NULL);		return TCL_ERROR;	    }	    return Tcl_RecordAndEval(interp, argv[2], 0);	}	return Tcl_RecordAndEval(interp, argv[2], TCL_NO_EVAL);    } else if ((c == 'c') && (strncmp(argv[1], "change", length)) == 0) {	if ((argc != 3) && (argc != 4)) {	    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],		    " change newValue ?event?\"", (char *) NULL);	    return TCL_ERROR;	}	if (argc == 3) {	    eventPtr = &iPtr->events[iPtr->curEvent];	    iPtr->revDisables += 1;	    while (iPtr->revPtr != NULL) {		HistoryRev *nextPtr;		ckfree(iPtr->revPtr->newBytes);		nextPtr = iPtr->revPtr->nextPtr;		ckfree((char *) iPtr->revPtr);		iPtr->revPtr = nextPtr;	    }	} else {	    eventPtr = GetEvent(iPtr, argv[3]);	    if (eventPtr == NULL) {		return TCL_ERROR;	    }	}	MakeSpace(eventPtr, strlen(argv[2]) + 1);	strcpy(eventPtr->command, argv[2]);	return TCL_OK;    } else if ((c == 'e') && (strncmp(argv[1], "event", length)) == 0) {	if (argc > 3) {	    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],		    " event ?event?\"", (char *) NULL);	    return TCL_ERROR;	}	eventPtr = GetEvent(iPtr, argc==2 ? "-1" : argv[2]);	if (eventPtr == NULL) {	    return TCL_ERROR;	}	RevResult(iPtr, eventPtr->command);	Tcl_SetResult(interp, eventPtr->command, TCL_VOLATILE);	return TCL_OK;    } else if ((c == 'i') && (strncmp(argv[1], "info", length)) == 0) {	long count;	int indx, i;	char *newline;	if ((argc != 2) && (argc != 3)) {	    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],		    " info ?count?\"", (char *) NULL);	    return TCL_ERROR;	}	infoCmd:	if (argc == 3) {	    if (Tcl_GetInt(interp, argv[2], &count) != TCL_OK) {		return TCL_ERROR;	    }	    if (count > iPtr->numEvents) {		count = iPtr->numEvents;	    }	} else {	    count = iPtr->numEvents;	}	newline = "";	for (i = 0, indx = iPtr->curEvent + 1 + iPtr->numEvents - count;		i < count; i++, indx++) {	    char *cur, *next, savedChar;	    char serial[20];	    if (indx >= iPtr->numEvents) {		indx -= iPtr->numEvents;	    }	    cur = iPtr->events[indx].command;	    if (*cur == '\0') {		continue;		/* No command recorded here. */	    }	    sprintf(serial, "%6d  ", iPtr->curEventNum + 1 - (count - i));	    Tcl_AppendResult(interp, newline, serial, (char *) NULL);	    newline = "\n";	    /*	     * Tricky formatting here:  for multi-line commands, indent	     * the continuation lines.	     */	    while (1) {		next = strchr(cur, '\n');		if (next == NULL) {		    break;		}		next++;		savedChar = *next;		*next = 0;		Tcl_AppendResult(interp, cur, "\t", (char *) NULL);		*next = savedChar;		cur = next;	    }	    Tcl_AppendResult(interp, cur, (char *) NULL);	}	return TCL_OK;    } else if ((c == 'k') && (strncmp(argv[1], "keep", length)) == 0) {	long count;	int i, src;	HistoryEvent *events;	if (argc != 3) {	    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],		    " keep number\"", (char *) NULL);	    return TCL_ERROR;	}	if (Tcl_GetInt(interp, argv[2], &count) != TCL_OK) {	    return TCL_ERROR;	}	if ((count <= 0) || (count > 1000)) {	    Tcl_AppendResult(interp, "illegal keep count \"", argv[2],		    "\"", (char *) NULL);	    return TCL_ERROR;	}	/*	 * Create a new history array and copy as much existing history	 * as possible from the old array.	 */	events = (HistoryEvent *)		ckalloc((unsigned) (count * sizeof(HistoryEvent)));	if (count < iPtr->numEvents) {	    src = iPtr->curEvent + 1 - count;	    if (src < 0) {		src += iPtr->numEvents;	    }	} else {	    src = iPtr->curEvent + 1;	}	for (i = 0; i < count; i++, src++) {	    if (src >= iPtr->numEvents) {		src = 0;	    }	    if (i < iPtr->numEvents) {		events[i] = iPtr->events[src];		iPtr->events[src].command = NULL;	    } else {		events[i].command = (char *) ckalloc(INITIAL_CMD_SIZE);		events[i].command[0] = 0;		events[i].bytesAvl = INITIAL_CMD_SIZE;	    }	}	/*	 * Throw away everything left in the old history array, and	 * substitute the new one for the old one.	 */	for (i = 0; i < iPtr->numEvents; i++) {	    if (iPtr->events[i].command != NULL) {		ckfree(iPtr->events[i].command);	    }	}	ckfree((char *) iPtr->events);	iPtr->events = events;	if (count < iPtr->numEvents) {	    iPtr->curEvent = count-1;	} else {	    iPtr->curEvent = iPtr->numEvents-1;	}	iPtr->numEvents = count;	return TCL_OK;    } else if ((c == 'n') && (strncmp(argv[1], "nextid", length)) == 0) {	if (argc != 2) {	    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],		    " nextid\"", (char *) NULL);	    return TCL_ERROR;	}	sprintf(iPtr->result, "%d", iPtr->curEventNum+1);	return TCL_OK;    } else if ((c == 'r') && (strncmp(argv[1], "redo", length)) == 0) {	if (argc > 3) {	    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],		    " redo ?event?\"", (char *) NULL);	    return TCL_ERROR;	}	eventPtr = GetEvent(iPtr, argc==2 ? "-1" : argv[2]);	if (eventPtr == NULL) {	    return TCL_ERROR;	}	RevCommand(iPtr, eventPtr->command);	return Tcl_Eval(interp, eventPtr->command, 0, (char **) NULL);    } else if ((c == 's') && (strncmp(argv[1], "substitute", length)) == 0) {	if ((argc > 5) || (argc < 4)) {	    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],		    " substitute old new ?event?\"", (char *) NULL);	    return TCL_ERROR;	}	eventPtr = GetEvent(iPtr, argc==4 ? "-1" : argv[4]);	if (eventPtr == NULL) {	    return TCL_ERROR;	}	return SubsAndEval(iPtr, eventPtr->command, argv[2], argv[3]);    } else if ((c == 'w') && (strncmp(argv[1], "words", length)) == 0) {	char *words;	if ((argc != 3) && (argc != 4)) {	    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],		    " words num-num/pat ?event?\"", (char *) NULL);	    return TCL_ERROR;	}	eventPtr = GetEvent(iPtr, argc==3 ? "-1" : argv[3]);	if (eventPtr == NULL) {	    return TCL_ERROR;	}	words = GetWords(iPtr, eventPtr->command, argv[2]);	if (words == NULL) {	    return TCL_ERROR;	}	RevResult(iPtr, words);	iPtr->result = words;	iPtr->freeProc = (Tcl_FreeProc *) free;	return TCL_OK;    }    Tcl_AppendResult(interp, "bad option \"", argv[1],	    "\": must be add, change, event, info, keep, nextid, ",	    "redo, substitute, or words", (char *) NULL);    return TCL_ERROR;}/* *---------------------------------------------------------------------- * * MakeSpace -- * *	Given a history event, make sure it has enough space for *	a string of a given length (enlarge the string area if *	necessary). * * Results: *	None. * * Side effects: *	More memory may get allocated. * *---------------------------------------------------------------------- */static voidMakeSpace(hPtr, size)    HistoryEvent *hPtr;    int size;			/* # of bytes needed in hPtr. */{    if (hPtr->bytesAvl < size) {	ckfree(hPtr->command);	hPtr->command = (char *) ckalloc((unsigned) size);	hPtr->bytesAvl = size;    }}/* *---------------------------------------------------------------------- * * InsertRev -- * *	Add a new revision to the list of those pending for iPtr. *	Do it in a way that keeps the revision list sorted in *	increasing order of firstIndex.  Also, eliminate revisions *	that are subsets of other revisions. * * Results: *	None.

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -