diff options
Diffstat (limited to 'generic/tclMain.c')
| -rw-r--r-- | generic/tclMain.c | 978 | 
1 files changed, 604 insertions, 374 deletions
| diff --git a/generic/tclMain.c b/generic/tclMain.c index 937d2aa..360f5e9 100644 --- a/generic/tclMain.c +++ b/generic/tclMain.c @@ -1,311 +1,436 @@ -/*  +/*   * tclMain.c --   *   *	Main program for Tcl shells and other Tcl-based applications. + *	This file contains a generic main program for Tcl shells and other + *	Tcl-based applications. It can be used as-is for many applications, + *	just by supplying a different appInitProc function for each specific + *	application. Or, it can be used as a template for creating new main + *	programs for Tcl applications.   *   * Copyright (c) 1988-1994 The Regents of the University of California.   * Copyright (c) 1994-1997 Sun Microsystems, Inc.   * Copyright (c) 2000 Ajuba Solutions.   * - * See the file "license.terms" for information on usage and redistribution - * of this file, and for a DISCLAIMER OF ALL WARRANTIES. - * - * RCS: @(#) $Id: tclMain.c,v 1.20.2.3 2006/05/05 18:08:58 dgp Exp $ + * See the file "license.terms" for information on usage and redistribution of + * this file, and for a DISCLAIMER OF ALL WARRANTIES. + */ + +/* + * On Windows, this file needs to be compiled twice, once with TCL_ASCII_MAIN + * defined. This way both Tcl_Main and Tcl_MainExW can be implemented, sharing + * the same source code.   */ -#include "tcl.h" +#if defined(TCL_ASCII_MAIN) +#   ifdef UNICODE +#	undef UNICODE +#	undef _UNICODE +#   else +#	define UNICODE +#	define _UNICODE +#   endif +#endif +  #include "tclInt.h" -# undef TCL_STORAGE_CLASS -# define TCL_STORAGE_CLASS DLLEXPORT +/* + * The default prompt used when the user has not overridden it. + */ + +#define DEFAULT_PRIMARY_PROMPT	"% "  /* - * Declarations for various library procedures and variables (don't want - * to include tclPort.h here, because people might copy this file out of - * the Tcl source directory to make their own modified versions). + * This file can be compiled on Windows in UNICODE mode, as well as on all + * other platforms using the native encoding. This is done by using the normal + * Windows functions like _tcscmp, but on platforms which don't have <tchar.h> + * we have to translate that to strcmp here.   */ -#if !defined(MAC_TCL) -extern int		isatty _ANSI_ARGS_((int fd)); -#else -#include <unistd.h> +#ifndef _WIN32 +#   define TCHAR char +#   define TEXT(arg) arg +#   define _tcscmp strcmp  #endif -static Tcl_Obj *tclStartupScriptPath = NULL; +/* + * Further on, in UNICODE mode we just use Tcl_NewUnicodeObj, otherwise + * NewNativeObj is needed (which provides proper conversion from native + * encoding to UTF-8). + */ + +#ifdef UNICODE +#   define NewNativeObj Tcl_NewUnicodeObj +#else /* !UNICODE */ +static inline Tcl_Obj * +NewNativeObj( +    char *string, +    int length) +{ +    Tcl_DString ds; + +    Tcl_ExternalToUtfDString(NULL, string, length, &ds); +    return TclDStringToObj(&ds); +} +#endif /* !UNICODE */ + +/* + * Declarations for various library functions and variables (don't want to + * include tclPort.h here, because people might copy this file out of the Tcl + * source directory to make their own modified versions). + */ + +extern CRTIMPORT int	isatty(int fd); + +/* + * The thread-local variables for this file's functions. + */ -static Tcl_MainLoopProc *mainLoopProc = NULL; +typedef struct { +    Tcl_Obj *path;		/* The filename of the script for *_Main() +				 * routines to [source] as a startup script, +				 * or NULL for none set, meaning enter +				 * interactive mode. */ +    Tcl_Obj *encoding;		/* The encoding of the startup script file. */ +    Tcl_MainLoopProc *mainLoopProc; +				/* Any installed main loop handler. The main +				 * extension that installs these is Tk. */ +} ThreadSpecificData; -/*  - * Structure definition for information used to keep the state of - * an interactive command processor that reads lines from standard - * input and writes prompts and results to standard output. +/* + * Structure definition for information used to keep the state of an + * interactive command processor that reads lines from standard input and + * writes prompts and results to standard output.   */  typedef enum { -    PROMPT_NONE,	/* Print no prompt */ -    PROMPT_START,	/* Print prompt for command start */ -    PROMPT_CONTINUE	/* Print prompt for command continuation */ +    PROMPT_NONE,		/* Print no prompt */ +    PROMPT_START,		/* Print prompt for command start */ +    PROMPT_CONTINUE		/* Print prompt for command continuation */  } PromptType;  typedef struct InteractiveState { -    Tcl_Channel input;		/* The standard input channel from which -				 * lines are read. */ -    int tty;                    /* Non-zero means standard input is a  -				 * terminal-like device.  Zero means it's -				 * a file. */ -    Tcl_Obj *commandPtr;	/* Used to assemble lines of input into -				 * Tcl commands. */ +    Tcl_Channel input;		/* The standard input channel from which lines +				 * are read. */ +    int tty;			/* Non-zero means standard input is a +				 * terminal-like device. Zero means it's a +				 * file. */ +    Tcl_Obj *commandPtr;	/* Used to assemble lines of input into Tcl +				 * commands. */      PromptType prompt;		/* Next prompt to print */      Tcl_Interp *interp;		/* Interpreter that evaluates interactive  				 * commands. */  } InteractiveState;  /* - * Forward declarations for procedures defined later in this file. + * Forward declarations for functions defined later in this file.   */ -static void		Prompt _ANSI_ARGS_((Tcl_Interp *interp, -			    PromptType *promptPtr)); -static void		StdinProc _ANSI_ARGS_((ClientData clientData, -			    int mask)); - +MODULE_SCOPE Tcl_MainLoopProc *TclGetMainLoop(void); +static void		Prompt(Tcl_Interp *interp, InteractiveState *isPtr); +static void		StdinProc(ClientData clientData, int mask); +static void		FreeMainInterp(ClientData clientData); +#ifndef TCL_ASCII_MAIN +static Tcl_ThreadDataKey dataKey; +  /*   *----------------------------------------------------------------------   * - * TclSetStartupScriptPath -- + * Tcl_SetStartupScript --   * - *	Primes the startup script VFS path, used to override the - *      command line processing. + *	Sets the path and encoding of the startup script to be evaluated by + *	Tcl_Main, used to override the command line processing.   *   * Results: - *	None.  + *	None.   *   * Side effects: - *	This procedure initializes the VFS path of the Tcl script to - *      run at startup.   *   *----------------------------------------------------------------------   */ -void TclSetStartupScriptPath(pathPtr) -    Tcl_Obj *pathPtr; + +void +Tcl_SetStartupScript( +    Tcl_Obj *path,		/* Filesystem path of startup script file */ +    const char *encoding)	/* Encoding of the data in that file */  { -    if (tclStartupScriptPath != NULL) { -	Tcl_DecrRefCount(tclStartupScriptPath); -    } -    tclStartupScriptPath = pathPtr; -    if (tclStartupScriptPath != NULL) { -	Tcl_IncrRefCount(tclStartupScriptPath); +    ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); +    Tcl_Obj *newEncoding = NULL; + +    if (encoding != NULL) { +	newEncoding = Tcl_NewStringObj(encoding, -1);      } -} +    if (tsdPtr->path != NULL) { +	Tcl_DecrRefCount(tsdPtr->path); +    } +    tsdPtr->path = path; +    if (tsdPtr->path != NULL) { +	Tcl_IncrRefCount(tsdPtr->path); +    } -/* - *---------------------------------------------------------------------- - * - * TclGetStartupScriptPath -- - * - *	Gets the startup script VFS path, used to override the - *      command line processing. - * - * Results: - *	The startup script VFS path, NULL if none has been set. - * - * Side effects: - *	None. - * - *---------------------------------------------------------------------- - */ -Tcl_Obj *TclGetStartupScriptPath() -{ -    return tclStartupScriptPath; +    if (tsdPtr->encoding != NULL) { +	Tcl_DecrRefCount(tsdPtr->encoding); +    } +    tsdPtr->encoding = newEncoding; +    if (tsdPtr->encoding != NULL) { +	Tcl_IncrRefCount(tsdPtr->encoding); +    }  } - - +  /*   *----------------------------------------------------------------------   * - * TclSetStartupScriptFileName -- + * Tcl_GetStartupScript --   * - *	Primes the startup script file name, used to override the - *      command line processing. + *	Gets the path and encoding of the startup script to be evaluated by + *	Tcl_Main.   *   * Results: - *	None.  + *	The path of the startup script; NULL if none has been set.   *   * Side effects: - *	This procedure initializes the file name of the Tcl script to - *      run at startup. + *	If encodingPtr is not NULL, stores a (const char *) in it pointing to + *	the encoding name registered for the startup script. Tcl retains + *	ownership of the string, and may free it. Caller should make a copy + *	for long-term use.   *   *----------------------------------------------------------------------   */ -void TclSetStartupScriptFileName(fileName) -    CONST char *fileName; + +Tcl_Obj * +Tcl_GetStartupScript( +    const char **encodingPtr)	/* When not NULL, points to storage for the +				 * (const char *) that points to the +				 * registered encoding name for the startup +				 * script. */  { -    Tcl_Obj *pathPtr = Tcl_NewStringObj(fileName,-1); -    TclSetStartupScriptPath(pathPtr); -} +    ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); +    if (encodingPtr != NULL) { +	if (tsdPtr->encoding == NULL) { +	    *encodingPtr = NULL; +	} else { +	    *encodingPtr = Tcl_GetString(tsdPtr->encoding); +	} +    } +    return tsdPtr->path; +} -/* - *---------------------------------------------------------------------- +/*----------------------------------------------------------------------   * - * TclGetStartupScriptFileName -- + * Tcl_SourceRCFile --   * - *	Gets the startup script file name, used to override the - *      command line processing. + *	This function is typically invoked by Tcl_Main of Tk_Main function to + *	source an application specific rc file into the interpreter at startup + *	time.   *   * Results: - *	The startup script file name, NULL if none has been set. + *	None.   *   * Side effects: - *	None. + *	Depends on what's in the rc script.   *   *----------------------------------------------------------------------   */ -CONST char *TclGetStartupScriptFileName() + +void +Tcl_SourceRCFile( +    Tcl_Interp *interp)		/* Interpreter to source rc file into. */  { -    Tcl_Obj *pathPtr = TclGetStartupScriptPath(); +    Tcl_DString temp; +    const char *fileName; +    Tcl_Channel chan; + +    fileName = Tcl_GetVar(interp, "tcl_rcFileName", TCL_GLOBAL_ONLY); +    if (fileName != NULL) { +	Tcl_Channel c; +	const char *fullName; + +	Tcl_DStringInit(&temp); +	fullName = Tcl_TranslateFileName(interp, fileName, &temp); +	if (fullName == NULL) { +	    /* +	     * Couldn't translate the file name (e.g. it referred to a bogus +	     * user or there was no HOME environment variable). Just do +	     * nothing. +	     */ +	} else { +	    /* +	     * Test for the existence of the rc file before trying to read it. +	     */ -    if (pathPtr == NULL) { -	return NULL; +	    c = Tcl_OpenFileChannel(NULL, fullName, "r", 0); +	    if (c != NULL) { +		Tcl_Close(NULL, c); +		if (Tcl_EvalFile(interp, fullName) != TCL_OK) { +		    chan = Tcl_GetStdChannel(TCL_STDERR); +		    if (chan) { +			Tcl_WriteObj(chan, Tcl_GetObjResult(interp)); +			Tcl_WriteChars(chan, "\n", 1); +		    } +		} +	    } +	} +	Tcl_DStringFree(&temp);      } -    return Tcl_GetString(pathPtr);  } - - +#endif /* !TCL_ASCII_MAIN */ -/* - *---------------------------------------------------------------------- +/*----------------------------------------------------------------------   * - * Tcl_Main -- + * Tcl_Main, Tcl_MainEx --   *   *	Main program for tclsh and most other Tcl-based applications.   *   * Results: - *	None. This procedure never returns (it exits the process when - *	it's done). + *	None. This function never returns (it exits the process when it's + *	done).   *   * Side effects: - *	This procedure initializes the Tcl world and then starts - *	interpreting commands;  almost anything could happen, depending - *	on the script being interpreted. + *	This function initializes the Tcl world and then starts interpreting + *	commands; almost anything could happen, depending on the script being + *	interpreted.   *   *----------------------------------------------------------------------   */  void -Tcl_Main(argc, argv, appInitProc) -    int argc;			/* Number of arguments. */ -    char **argv;		/* Array of argument strings. */ -    Tcl_AppInitProc *appInitProc; +Tcl_MainEx( +    int argc,			/* Number of arguments. */ +    TCHAR **argv,		/* Array of argument strings. */ +    Tcl_AppInitProc *appInitProc,  				/* Application-specific initialization -				 * procedure to call after most -				 * initialization but before starting to -				 * execute commands. */ +				 * function to call after most initialization +				 * but before starting to execute commands. */ +    Tcl_Interp *interp)  { -    Tcl_Obj *resultPtr, *argvPtr, *commandPtr = NULL; -    PromptType prompt = PROMPT_START; -    int code, length, tty, exitCode = 0; -    Tcl_Channel inChannel, outChannel, errChannel; -    Tcl_Interp *interp; -    Tcl_DString appName; -    Tcl_Obj *objPtr; +    Tcl_Obj *path, *resultPtr, *argvPtr, *appName; +    const char *encodingName = NULL; +    int code, exitCode = 0; +    Tcl_MainLoopProc *mainLoopProc; +    Tcl_Channel chan; +    InteractiveState is; -    Tcl_FindExecutable(argv[0]); +    TclpSetInitialEncodings(); +    TclpFindExecutable((const char *)argv[0]); -    interp = Tcl_CreateInterp();      Tcl_InitMemory(interp); +    is.interp = interp; +    is.prompt = PROMPT_START; +    is.commandPtr = Tcl_NewObj(); +      /* -     * Make command-line arguments available in the Tcl variables "argc" -     * and "argv".  If the first argument doesn't start with a "-" then -     * strip it off and use it as the name of a script file to process. +     * If the application has not already set a startup script, parse the +     * first few command line arguments to determine the script path and +     * encoding.       */ -    if (TclGetStartupScriptPath() == NULL) { -	if ((argc > 1) && (argv[1][0] != '-')) { -	    TclSetStartupScriptFileName(argv[1]); +    if (NULL == Tcl_GetStartupScript(NULL)) { +	/* +	 * Check whether first 3 args (argv[1] - argv[3]) look like +	 *  -encoding ENCODING FILENAME +	 * or like +	 *  FILENAME +	 */ + +	if ((argc > 3) && (0 == _tcscmp(TEXT("-encoding"), argv[1])) +		&& ('-' != argv[3][0])) { +	    Tcl_Obj *value = NewNativeObj(argv[2], -1); +	    Tcl_SetStartupScript(NewNativeObj(argv[3], -1), +		    Tcl_GetString(value)); +	    Tcl_DecrRefCount(value); +	    argc -= 3; +	    argv += 3; +	} else if ((argc > 1) && ('-' != argv[1][0])) { +	    Tcl_SetStartupScript(NewNativeObj(argv[1], -1), NULL);  	    argc--;  	    argv++;  	}      } -    if (TclGetStartupScriptPath() == NULL) { -	Tcl_ExternalToUtfDString(NULL, argv[0], -1, &appName); +    path = Tcl_GetStartupScript(&encodingName); +    if (path == NULL) { +	appName = NewNativeObj(argv[0], -1);      } else { -	TclSetStartupScriptFileName(Tcl_ExternalToUtfDString(NULL, -		TclGetStartupScriptFileName(), -1, &appName)); +	appName = path;      } -    Tcl_SetVar(interp, "argv0", Tcl_DStringValue(&appName), TCL_GLOBAL_ONLY); -    Tcl_DStringFree(&appName); +    Tcl_SetVar2Ex(interp, "argv0", NULL, appName, TCL_GLOBAL_ONLY);      argc--;      argv++; -    objPtr = Tcl_NewIntObj(argc); -    Tcl_IncrRefCount(objPtr); -    Tcl_SetVar2Ex(interp, "argc", NULL, objPtr, TCL_GLOBAL_ONLY); -    Tcl_DecrRefCount(objPtr); -     +    Tcl_SetVar2Ex(interp, "argc", NULL, Tcl_NewIntObj(argc), TCL_GLOBAL_ONLY); +      argvPtr = Tcl_NewListObj(0, NULL);      while (argc--) { -	Tcl_DString ds; -	Tcl_ExternalToUtfDString(NULL, *argv++, -1, &ds); -	Tcl_ListObjAppendElement(NULL, argvPtr, Tcl_NewStringObj( -		Tcl_DStringValue(&ds), Tcl_DStringLength(&ds))); -	Tcl_DStringFree(&ds); +	Tcl_ListObjAppendElement(NULL, argvPtr, NewNativeObj(*argv++, -1));      } -    Tcl_IncrRefCount(argvPtr);      Tcl_SetVar2Ex(interp, "argv", NULL, argvPtr, TCL_GLOBAL_ONLY); -    Tcl_DecrRefCount(argvPtr);      /*       * Set the "tcl_interactive" variable.       */ -    tty = isatty(0); -    Tcl_SetVar(interp, "tcl_interactive", -	    ((TclGetStartupScriptPath() == NULL) && tty) ? "1" : "0", -	    TCL_GLOBAL_ONLY); -     +    is.tty = isatty(0); +    Tcl_SetVar2Ex(interp, "tcl_interactive", NULL, +	    Tcl_NewIntObj(!path && is.tty), TCL_GLOBAL_ONLY); +      /*       * Invoke application-specific initialization.       */ -    Tcl_Preserve((ClientData) interp); -    if ((*appInitProc)(interp) != TCL_OK) { -	errChannel = Tcl_GetStdChannel(TCL_STDERR); -	if (errChannel) { -	    Tcl_WriteChars(errChannel, +    Tcl_Preserve(interp); +    if (appInitProc(interp) != TCL_OK) { +	chan = Tcl_GetStdChannel(TCL_STDERR); +	if (chan) { +	    Tcl_WriteChars(chan,  		    "application-specific initialization failed: ", -1); -	    Tcl_WriteObj(errChannel, Tcl_GetObjResult(interp)); -	    Tcl_WriteChars(errChannel, "\n", 1); +	    Tcl_WriteObj(chan, Tcl_GetObjResult(interp)); +	    Tcl_WriteChars(chan, "\n", 1);  	}      }      if (Tcl_InterpDeleted(interp)) {  	goto done;      } +    if (Tcl_LimitExceeded(interp)) { +	goto done; +    } +    if (TclFullFinalizationRequested()) { +	/* +	 * Arrange for final deletion of the main interp +	 */ + +	/* ARGH Munchhausen effect */ +	Tcl_CreateExitHandler(FreeMainInterp, interp); +    }      /* -     * If a script file was specified then just source that file -     * and quit. +     * Invoke the script specified on the command line, if any. Must fetch it +     * again, as the appInitProc might have reset it.       */ -    if (TclGetStartupScriptPath() != NULL) { -	code = Tcl_FSEvalFile(interp, TclGetStartupScriptPath()); +    path = Tcl_GetStartupScript(&encodingName); +    if (path != NULL) { +	Tcl_ResetResult(interp); +	code = Tcl_FSEvalFileEx(interp, path, encodingName);  	if (code != TCL_OK) { -	    errChannel = Tcl_GetStdChannel(TCL_STDERR); -	    if (errChannel) { - -		/* -		 * The following statement guarantees that the errorInfo -		 * variable is set properly. -		 */ - -		Tcl_AddErrorInfo(interp, ""); -		Tcl_WriteObj(errChannel, Tcl_GetVar2Ex(interp, "errorInfo", -			NULL, TCL_GLOBAL_ONLY)); -		Tcl_WriteChars(errChannel, "\n", 1); +	    chan = Tcl_GetStdChannel(TCL_STDERR); +	    if (chan) { +		Tcl_Obj *options = Tcl_GetReturnOptions(interp, code); +		Tcl_Obj *keyPtr, *valuePtr; + +		TclNewLiteralStringObj(keyPtr, "-errorinfo"); +		Tcl_IncrRefCount(keyPtr); +		Tcl_DictObjGet(NULL, options, keyPtr, &valuePtr); +		Tcl_DecrRefCount(keyPtr); + +		if (valuePtr) { +		    Tcl_WriteObj(chan, valuePtr); +		} +		Tcl_WriteChars(chan, "\n", 1); +		Tcl_DecrRefCount(options);  	    }  	    exitCode = 1;  	} @@ -313,272 +438,338 @@ Tcl_Main(argc, argv, appInitProc)      }      /* -     * We're running interactively.  Source a user-specific startup -     * file if the application specified one and if the file exists. +     * We're running interactively. Source a user-specific startup file if the +     * application specified one and if the file exists.       */      Tcl_SourceRCFile(interp); +    if (Tcl_LimitExceeded(interp)) { +	goto done; +    }      /* -     * Process commands from stdin until there's an end-of-file.  Note -     * that we need to fetch the standard channels again after every -     * eval, since they may have been changed. +     * Process commands from stdin until there's an end-of-file. Note that we +     * need to fetch the standard channels again after every eval, since they +     * may have been changed.       */ -    commandPtr = Tcl_NewObj(); -    Tcl_IncrRefCount(commandPtr); +    Tcl_IncrRefCount(is.commandPtr);      /*       * Get a new value for tty if anyone writes to ::tcl_interactive       */ -    Tcl_LinkVar(interp, "tcl_interactive", (char *) &tty, TCL_LINK_BOOLEAN); -    inChannel = Tcl_GetStdChannel(TCL_STDIN); -    outChannel = Tcl_GetStdChannel(TCL_STDOUT); -    while ((inChannel != (Tcl_Channel) NULL) && !Tcl_InterpDeleted(interp)) { + +    Tcl_LinkVar(interp, "tcl_interactive", (char *) &is.tty, TCL_LINK_BOOLEAN); +    is.input = Tcl_GetStdChannel(TCL_STDIN); +    while ((is.input != NULL) && !Tcl_InterpDeleted(interp)) { +	mainLoopProc = TclGetMainLoop();  	if (mainLoopProc == NULL) { -	    if (tty) { -		Prompt(interp, &prompt); +	    int length; + +	    if (is.tty) { +		Prompt(interp, &is);  		if (Tcl_InterpDeleted(interp)) {  		    break;  		} -		inChannel = Tcl_GetStdChannel(TCL_STDIN); -		if (inChannel == (Tcl_Channel) NULL) { -	            break; +		if (Tcl_LimitExceeded(interp)) { +		    break; +		} +		is.input = Tcl_GetStdChannel(TCL_STDIN); +		if (is.input == NULL) { +		    break;  		}  	    } -	    if (Tcl_IsShared(commandPtr)) { -		Tcl_DecrRefCount(commandPtr); -		commandPtr = Tcl_DuplicateObj(commandPtr); -		Tcl_IncrRefCount(commandPtr); +	    if (Tcl_IsShared(is.commandPtr)) { +		Tcl_DecrRefCount(is.commandPtr); +		is.commandPtr = Tcl_DuplicateObj(is.commandPtr); +		Tcl_IncrRefCount(is.commandPtr);  	    } -            length = Tcl_GetsObj(inChannel, commandPtr); +	    length = Tcl_GetsObj(is.input, is.commandPtr);  	    if (length < 0) { -		if (Tcl_InputBlocked(inChannel)) { - +		if (Tcl_InputBlocked(is.input)) {  		    /*  		     * This can only happen if stdin has been set to -		     * non-blocking.  In that case cycle back and try -		     * again.  This sets up a tight polling loop (since -		     * we have no event loop running).  If this causes -		     * bad CPU hogging, we might try toggling the blocking -		     * on stdin instead. +		     * non-blocking. In that case cycle back and try again. +		     * This sets up a tight polling loop (since we have no +		     * event loop running). If this causes bad CPU hogging, we +		     * might try toggling the blocking on stdin instead.  		     */  		    continue;  		} -		/*  +		/*  		 * Either EOF, or an error on stdin; we're done  		 */  		break;  	    } -            /* -             * Add the newline removed by Tcl_GetsObj back to the string. -             */ +	    /* +	     * Add the newline removed by Tcl_GetsObj back to the string. Have +	     * to add it back before testing completeness, because it can make +	     * a difference. [Bug 1775878] +	     */ -	    if (Tcl_IsShared(commandPtr)) { -		Tcl_DecrRefCount(commandPtr); -		commandPtr = Tcl_DuplicateObj(commandPtr); -		Tcl_IncrRefCount(commandPtr); +	    if (Tcl_IsShared(is.commandPtr)) { +		Tcl_DecrRefCount(is.commandPtr); +		is.commandPtr = Tcl_DuplicateObj(is.commandPtr); +		Tcl_IncrRefCount(is.commandPtr);  	    } -	    Tcl_AppendToObj(commandPtr, "\n", 1); -	    if (!TclObjCommandComplete(commandPtr)) { -		prompt = PROMPT_CONTINUE; +	    Tcl_AppendToObj(is.commandPtr, "\n", 1); +	    if (!TclObjCommandComplete(is.commandPtr)) { +		is.prompt = PROMPT_CONTINUE;  		continue;  	    } -	    prompt = PROMPT_START; -	    code = Tcl_RecordAndEvalObj(interp, commandPtr, TCL_EVAL_GLOBAL); -	    inChannel = Tcl_GetStdChannel(TCL_STDIN); -	    outChannel = Tcl_GetStdChannel(TCL_STDOUT); -	    errChannel = Tcl_GetStdChannel(TCL_STDERR); -	    Tcl_DecrRefCount(commandPtr); -	    commandPtr = Tcl_NewObj(); -	    Tcl_IncrRefCount(commandPtr); +	    is.prompt = PROMPT_START; + +	    /* +	     * The final newline is syntactically redundant, and causes some +	     * error messages troubles deeper in, so lop it back off. +	     */ + +	    Tcl_GetStringFromObj(is.commandPtr, &length); +	    Tcl_SetObjLength(is.commandPtr, --length); +	    code = Tcl_RecordAndEvalObj(interp, is.commandPtr, +		    TCL_EVAL_GLOBAL); +	    is.input = Tcl_GetStdChannel(TCL_STDIN); +	    Tcl_DecrRefCount(is.commandPtr); +	    is.commandPtr = Tcl_NewObj(); +	    Tcl_IncrRefCount(is.commandPtr);  	    if (code != TCL_OK) { -		if (errChannel) { -		    Tcl_WriteObj(errChannel, Tcl_GetObjResult(interp)); -		    Tcl_WriteChars(errChannel, "\n", 1); +		chan = Tcl_GetStdChannel(TCL_STDERR); +		if (chan) { +		    Tcl_WriteObj(chan, Tcl_GetObjResult(interp)); +		    Tcl_WriteChars(chan, "\n", 1);  		} -	    } else if (tty) { +	    } else if (is.tty) {  		resultPtr = Tcl_GetObjResult(interp);  		Tcl_IncrRefCount(resultPtr);  		Tcl_GetStringFromObj(resultPtr, &length); -		if ((length > 0) && outChannel) { -		    Tcl_WriteObj(outChannel, resultPtr); -		    Tcl_WriteChars(outChannel, "\n", 1); +		chan = Tcl_GetStdChannel(TCL_STDOUT); +		if ((length > 0) && chan) { +		    Tcl_WriteObj(chan, resultPtr); +		    Tcl_WriteChars(chan, "\n", 1);  		}  		Tcl_DecrRefCount(resultPtr);  	    }  	} else {	/* (mainLoopProc != NULL) */  	    /* -	     * If a main loop has been defined while running interactively, -	     * we want to start a fileevent based prompt by establishing a +	     * If a main loop has been defined while running interactively, we +	     * want to start a fileevent based prompt by establishing a  	     * channel handler for stdin.  	     */ -	    InteractiveState *isPtr = NULL; - -	    if (inChannel) { -	        if (tty) { -		    Prompt(interp, &prompt); -	        } -		isPtr = (InteractiveState *)  -			ckalloc((int) sizeof(InteractiveState)); -		isPtr->input = inChannel; -		isPtr->tty = tty; -		isPtr->commandPtr = commandPtr; -		isPtr->prompt = prompt; -		isPtr->interp = interp; - -		Tcl_UnlinkVar(interp, "tcl_interactive"); -		Tcl_LinkVar(interp, "tcl_interactive", (char *) &(isPtr->tty), -			TCL_LINK_BOOLEAN); - -		Tcl_CreateChannelHandler(inChannel, TCL_READABLE, StdinProc, -			(ClientData) isPtr); +	    if (is.input) { +		if (is.tty) { +		    Prompt(interp, &is); +		} + +		Tcl_CreateChannelHandler(is.input, TCL_READABLE, +			StdinProc, &is);  	    } -	    (*mainLoopProc)(); -	    mainLoopProc = NULL; - -	    if (inChannel) { -		tty = isPtr->tty; -		Tcl_UnlinkVar(interp, "tcl_interactive"); -		Tcl_LinkVar(interp, "tcl_interactive", (char *) &tty, -			TCL_LINK_BOOLEAN); -		prompt = isPtr->prompt; -		commandPtr = isPtr->commandPtr; -		if (isPtr->input != (Tcl_Channel) NULL) { -		    Tcl_DeleteChannelHandler(isPtr->input, StdinProc, -			    (ClientData) isPtr); -		} -		ckfree((char *)isPtr); +	    mainLoopProc(); +	    Tcl_SetMainLoop(NULL); + +	    if (is.input) { +		Tcl_DeleteChannelHandler(is.input, StdinProc, &is);  	    } -	    inChannel = Tcl_GetStdChannel(TCL_STDIN); -	    outChannel = Tcl_GetStdChannel(TCL_STDOUT); -	    errChannel = Tcl_GetStdChannel(TCL_STDERR); +	    is.input = Tcl_GetStdChannel(TCL_STDIN);  	} -#ifdef TCL_MEM_DEBUG  	/* -	 * This code here only for the (unsupported and deprecated) -	 * [checkmem] command. +	 * This code here only for the (unsupported and deprecated) [checkmem] +	 * command.  	 */ +#ifdef TCL_MEM_DEBUG  	if (tclMemDumpFileName != NULL) { -	    mainLoopProc = NULL; +	    Tcl_SetMainLoop(NULL);  	    Tcl_DeleteInterp(interp);  	} -#endif +#endif /* TCL_MEM_DEBUG */      } -    done: -    if ((exitCode == 0) && (mainLoopProc != NULL)) { - +  done: +    mainLoopProc = TclGetMainLoop(); +    if ((exitCode == 0) && mainLoopProc && !Tcl_LimitExceeded(interp)) {  	/* -	 * If everything has gone OK so far, call the main loop proc, -	 * if it exists.  Packages (like Tk) can set it to start processing -	 * events at this point. +	 * If everything has gone OK so far, call the main loop proc, if it +	 * exists. Packages (like Tk) can set it to start processing events at +	 * this point.  	 */ -	(*mainLoopProc)(); -	mainLoopProc = NULL; +	mainLoopProc(); +	Tcl_SetMainLoop(NULL);      } -    if (commandPtr != NULL) { -	Tcl_DecrRefCount(commandPtr); +    if (is.commandPtr != NULL) { +	Tcl_DecrRefCount(is.commandPtr);      }      /* -     * Rather than calling exit, invoke the "exit" command so that -     * users can replace "exit" with some other command to do additional -     * cleanup on exit.  The Tcl_Eval call should never return. +     * Rather than calling exit, invoke the "exit" command so that users can +     * replace "exit" with some other command to do additional cleanup on +     * exit. The Tcl_EvalObjEx call should never return.       */ -    if (!Tcl_InterpDeleted(interp)) { -	char buffer[TCL_INTEGER_SPACE + 5]; -        sprintf(buffer, "exit %d", exitCode); -        Tcl_Eval(interp, buffer); - -        /* -         * If Tcl_Eval returns, trying to eval [exit], something -         * unusual is happening.  Maybe interp has been deleted; -         * maybe [exit] was redefined.  We still want to cleanup -         * and exit. -         */ - -        if (!Tcl_InterpDeleted(interp)) { -            Tcl_DeleteInterp(interp); -        } +    if (!Tcl_InterpDeleted(interp) && !Tcl_LimitExceeded(interp)) { +	Tcl_Obj *cmd = Tcl_ObjPrintf("exit %d", exitCode); +	     +	Tcl_IncrRefCount(cmd); +	Tcl_EvalObjEx(interp, cmd, TCL_EVAL_GLOBAL); +	Tcl_DecrRefCount(cmd);      } -    TclSetStartupScriptPath(NULL);      /* -     * If we get here, the master interp has been deleted.  Allow -     * its destruction with the last matching Tcl_Release. +     * If Tcl_EvalObjEx returns, trying to eval [exit], something unusual is +     * happening. Maybe interp has been deleted; maybe [exit] was redefined, +     * maybe we've blown up because of an exceeded limit. We still want to +     * cleanup and exit.       */ -    Tcl_Release((ClientData) interp);      Tcl_Exit(exitCode);  } + +#if (TCL_MAJOR_VERSION == 8) && !defined(UNICODE) +#undef Tcl_Main +extern DLLEXPORT void +Tcl_Main( +    int argc,			/* Number of arguments. */ +    char **argv,		/* Array of argument strings. */ +    Tcl_AppInitProc *appInitProc) +				/* Application-specific initialization +				 * function to call after most initialization +				 * but before starting to execute commands. */ +{ +    Tcl_MainEx(argc, argv, appInitProc, Tcl_CreateInterp()); +} +#endif /* TCL_MAJOR_VERSION == 8 && !UNICODE */ +#ifndef TCL_ASCII_MAIN +  /*   *---------------------------------------------------------------   *   * Tcl_SetMainLoop --   * - *	Sets an alternative main loop procedure. + *	Sets an alternative main loop function.   *   * Results: - *	Returns the previously defined main loop procedure. + *	None.   *   * Side effects: - *	This procedure will be called before Tcl exits, allowing for - *	the creation of an event loop. + *	This function will be called before Tcl exits, allowing for the + *	creation of an event loop.   *   *---------------------------------------------------------------   */  void -Tcl_SetMainLoop(proc) -    Tcl_MainLoopProc *proc; +Tcl_SetMainLoop( +    Tcl_MainLoopProc *proc) +{ +    ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + +    tsdPtr->mainLoopProc = proc; +} + +/* + *--------------------------------------------------------------- + * + * TclGetMainLoop -- + * + *	Returns the current alternative main loop function. + * + * Results: + *	Returns the previously defined main loop function, or NULL to indicate + *	that no such function has been installed and standard tclsh behaviour + *	(i.e., exit once the script is evaluated if not interactive) is + *	requested.. + * + * Side effects: + *	None (other than possible creation of this file's TSD block). + * + *--------------------------------------------------------------- + */ + +Tcl_MainLoopProc * +TclGetMainLoop(void) +{ +    ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + +    return tsdPtr->mainLoopProc; +} + +/* + *---------------------------------------------------------------------- + * + * TclFullFinalizationRequested -- + * + *	This function returns true when either -DPURIFY is specified, or the + *	environment variable TCL_FINALIZE_ON_EXIT is set and not "0". This + *	predicate is called at places affecting the exit sequence, so that the + *	default behavior is a fast and deadlock-free exit, and the modified + *	behavior is a more thorough finalization for debugging purposes (leak + *	hunting etc). + * + * Results: + *	A boolean. + * + *---------------------------------------------------------------------- + */ + +MODULE_SCOPE int +TclFullFinalizationRequested(void)  { -    mainLoopProc = proc; +#ifdef PURIFY +    return 1; +#else +    const char *fin; +    Tcl_DString ds; +    int finalize = 0; +     +    fin = TclGetEnv("TCL_FINALIZE_ON_EXIT", &ds); +    finalize = ((fin != NULL) && strcmp(fin, "0")); +    if (fin != NULL) { +	Tcl_DStringFree(&ds); +    } +    return finalize; +#endif /* PURIFY */  } +#endif /* !TCL_ASCII_MAIN */  /*   *----------------------------------------------------------------------   *   * StdinProc --   * - *	This procedure is invoked by the event dispatcher whenever - *	standard input becomes readable.  It grabs the next line of - *	input characters, adds them to a command being assembled, and - *	executes the command if it's complete. + *	This function is invoked by the event dispatcher whenever standard + *	input becomes readable. It grabs the next line of input characters, + *	adds them to a command being assembled, and executes the command if + *	it's complete.   *   * Results:   *	None.   *   * Side effects: - *	Could be almost arbitrary, depending on the command that's - *	typed. + *	Could be almost arbitrary, depending on the command that's typed.   *   *----------------------------------------------------------------------   */      /* ARGSUSED */  static void -StdinProc(clientData, mask) -    ClientData clientData;		/* The state of interactive cmd line */ -    int mask;				/* Not used. */ +StdinProc( +    ClientData clientData,	/* The state of interactive cmd line */ +    int mask)			/* Not used. */  { -    InteractiveState *isPtr = (InteractiveState *) clientData; +    int code, length; +    InteractiveState *isPtr = clientData;      Tcl_Channel chan = isPtr->input;      Tcl_Obj *commandPtr = isPtr->commandPtr;      Tcl_Interp *interp = isPtr->interp; -    int code, length;      if (Tcl_IsShared(commandPtr)) {  	Tcl_DecrRefCount(commandPtr); @@ -592,13 +783,14 @@ StdinProc(clientData, mask)  	}  	if (isPtr->tty) {  	    /* -	     * Would be better to find a way to exit the mainLoop? -	     * Or perhaps evaluate [exit]?  Leaving as is for now due -	     * to compatibility concerns. +	     * Would be better to find a way to exit the mainLoop? Or perhaps +	     * evaluate [exit]? Leaving as is for now due to compatibility +	     * concerns.  	     */ +  	    Tcl_Exit(0);  	} -	Tcl_DeleteChannelHandler(chan, StdinProc, (ClientData) isPtr); +	Tcl_DeleteChannelHandler(chan, StdinProc, isPtr);  	return;      } @@ -609,43 +801,45 @@ StdinProc(clientData, mask)      }      Tcl_AppendToObj(commandPtr, "\n", 1);      if (!TclObjCommandComplete(commandPtr)) { -        isPtr->prompt = PROMPT_CONTINUE; -        goto prompt; +	isPtr->prompt = PROMPT_CONTINUE; +	goto prompt;      }      isPtr->prompt = PROMPT_START; +    Tcl_GetStringFromObj(commandPtr, &length); +    Tcl_SetObjLength(commandPtr, --length);      /*       * Disable the stdin channel handler while evaluating the command; -     * otherwise if the command re-enters the event loop we might -     * process commands from stdin before the current command is -     * finished.  Among other things, this will trash the text of the -     * command being evaluated. +     * otherwise if the command re-enters the event loop we might process +     * commands from stdin before the current command is finished. Among other +     * things, this will trash the text of the command being evaluated.       */ -    Tcl_CreateChannelHandler(chan, 0, StdinProc, (ClientData) isPtr); +    Tcl_CreateChannelHandler(chan, 0, StdinProc, isPtr);      code = Tcl_RecordAndEvalObj(interp, commandPtr, TCL_EVAL_GLOBAL);      isPtr->input = chan = Tcl_GetStdChannel(TCL_STDIN);      Tcl_DecrRefCount(commandPtr);      isPtr->commandPtr = commandPtr = Tcl_NewObj();      Tcl_IncrRefCount(commandPtr); -    if (chan != (Tcl_Channel) NULL) { -	Tcl_CreateChannelHandler(chan, TCL_READABLE, StdinProc, -		(ClientData) isPtr); +    if (chan != NULL) { +	Tcl_CreateChannelHandler(chan, TCL_READABLE, StdinProc, isPtr);      }      if (code != TCL_OK) { -	Tcl_Channel errChannel = Tcl_GetStdChannel(TCL_STDERR); -	if (errChannel != (Tcl_Channel) NULL) { -	    Tcl_WriteObj(errChannel, Tcl_GetObjResult(interp)); -	    Tcl_WriteChars(errChannel, "\n", 1); +	chan = Tcl_GetStdChannel(TCL_STDERR); + +	if (chan != NULL) { +	    Tcl_WriteObj(chan, Tcl_GetObjResult(interp)); +	    Tcl_WriteChars(chan, "\n", 1);  	}      } else if (isPtr->tty) {  	Tcl_Obj *resultPtr = Tcl_GetObjResult(interp); -	Tcl_Channel outChannel = Tcl_GetStdChannel(TCL_STDOUT); +	chan = Tcl_GetStdChannel(TCL_STDOUT); +  	Tcl_IncrRefCount(resultPtr);  	Tcl_GetStringFromObj(resultPtr, &length); -	if ((length >0) && (outChannel != (Tcl_Channel) NULL)) { -	    Tcl_WriteObj(outChannel, resultPtr); -	    Tcl_WriteChars(outChannel, "\n", 1); +	if ((length > 0) && (chan != NULL)) { +	    Tcl_WriteObj(chan, resultPtr); +	    Tcl_WriteChars(chan, "\n", 1);  	}  	Tcl_DecrRefCount(resultPtr);      } @@ -654,9 +848,9 @@ StdinProc(clientData, mask)       * If a tty stdin is still around, output a prompt.       */ -    prompt: -    if (isPtr->tty && (isPtr->input != (Tcl_Channel) NULL)) { -	Prompt(interp, &(isPtr->prompt)); +  prompt: +    if (isPtr->tty && (isPtr->input != NULL)) { +	Prompt(interp, isPtr);  	isPtr->input = Tcl_GetStdChannel(TCL_STDIN);      }  } @@ -666,63 +860,99 @@ StdinProc(clientData, mask)   *   * Prompt --   * - *	Issue a prompt on standard output, or invoke a script - *	to issue the prompt. + *	Issue a prompt on standard output, or invoke a script to issue the + *	prompt.   *   * Results:   *	None.   *   * Side effects: - *	A prompt gets output, and a Tcl script may be evaluated - *	in interp. + *	A prompt gets output, and a Tcl script may be evaluated in interp.   *   *----------------------------------------------------------------------   */  static void -Prompt(interp, promptPtr) -    Tcl_Interp *interp;			/* Interpreter to use for prompting. */ -    PromptType *promptPtr;		/* Points to type of prompt to print. -					 * Filled with PROMPT_NONE after a -					 * prompt is printed. */ +Prompt( +    Tcl_Interp *interp,		/* Interpreter to use for prompting. */ +    InteractiveState *isPtr)	/* InteractiveState. Filled with PROMPT_NONE +				 * after a prompt is printed. */  {      Tcl_Obj *promptCmdPtr;      int code; -    Tcl_Channel outChannel, errChannel; +    Tcl_Channel chan; -    if (*promptPtr == PROMPT_NONE) { +    if (isPtr->prompt == PROMPT_NONE) {  	return;      }      promptCmdPtr = Tcl_GetVar2Ex(interp, -	    ((*promptPtr == PROMPT_CONTINUE) ? "tcl_prompt2" : "tcl_prompt1"), +	    (isPtr->prompt==PROMPT_CONTINUE ? "tcl_prompt2" : "tcl_prompt1"),  	    NULL, TCL_GLOBAL_ONLY); +      if (Tcl_InterpDeleted(interp)) {  	return;      }      if (promptCmdPtr == NULL) { -	defaultPrompt: -	outChannel = Tcl_GetStdChannel(TCL_STDOUT); -	if ((*promptPtr == PROMPT_START) -		&& (outChannel != (Tcl_Channel) NULL)) { -	    Tcl_WriteChars(outChannel, "% ", 2); +    defaultPrompt: +	if (isPtr->prompt == PROMPT_START) { +	    chan = Tcl_GetStdChannel(TCL_STDOUT); +	    if (chan != NULL) { +		Tcl_WriteChars(chan, DEFAULT_PRIMARY_PROMPT, +			strlen(DEFAULT_PRIMARY_PROMPT)); +	    }  	}      } else {  	code = Tcl_EvalObjEx(interp, promptCmdPtr, TCL_EVAL_GLOBAL);  	if (code != TCL_OK) {  	    Tcl_AddErrorInfo(interp,  		    "\n    (script that generates prompt)"); -	    errChannel = Tcl_GetStdChannel(TCL_STDERR); -            if (errChannel != (Tcl_Channel) NULL) { -                Tcl_WriteObj(errChannel, Tcl_GetObjResult(interp)); -                Tcl_WriteChars(errChannel, "\n", 1); -            } +	    chan = Tcl_GetStdChannel(TCL_STDERR); +	    if (chan != NULL) { +		Tcl_WriteObj(chan, Tcl_GetObjResult(interp)); +		Tcl_WriteChars(chan, "\n", 1); +	    }  	    goto defaultPrompt;  	}      } -    outChannel = Tcl_GetStdChannel(TCL_STDOUT); -    if (outChannel != (Tcl_Channel) NULL) { -	Tcl_Flush(outChannel); + +    chan = Tcl_GetStdChannel(TCL_STDOUT); +    if (chan != NULL) { +	Tcl_Flush(chan); +    } +    isPtr->prompt = PROMPT_NONE; +} + +/* + *---------------------------------------------------------------------- + * + * FreeMainInterp -- + * + *	Exit handler used to cleanup the main interpreter and ancillary + *	startup script storage at exit. + * + *---------------------------------------------------------------------- + */ + +static void +FreeMainInterp( +    ClientData clientData) +{ +    Tcl_Interp *interp = clientData; + +    /*if (TclInExit()) return;*/ + +    if (!Tcl_InterpDeleted(interp)) { +	Tcl_DeleteInterp(interp);      } -    *promptPtr = PROMPT_NONE; +    Tcl_SetStartupScript(NULL, NULL); +    Tcl_Release(interp);  } + +/* + * Local Variables: + * mode: c + * c-basic-offset: 4 + * fill-column: 78 + * End: + */ | 
