diff options
Diffstat (limited to 'generic/tclMain.c')
-rw-r--r-- | generic/tclMain.c | 976 |
1 files changed, 604 insertions, 372 deletions
diff --git a/generic/tclMain.c b/generic/tclMain.c index 8252170..360f5e9 100644 --- a/generic/tclMain.c +++ b/generic/tclMain.c @@ -1,309 +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. + * 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; } @@ -311,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) { - 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) +{ +#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); @@ -590,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; } @@ -607,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); } @@ -652,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); } } @@ -664,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); } - *promptPtr = PROMPT_NONE; + 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); + } + Tcl_SetStartupScript(NULL, NULL); + Tcl_Release(interp); +} + +/* + * Local Variables: + * mode: c + * c-basic-offset: 4 + * fill-column: 78 + * End: + */ |