341 lines
9.3 KiB
C
341 lines
9.3 KiB
C
/*
|
||
* tclMain.c --
|
||
*
|
||
* Main program for Tcl shells and other Tcl-based applications.
|
||
*
|
||
* Copyright (c) 1988-1994 The Regents of the University of California.
|
||
* Copyright (c) 1994-1996 Sun Microsystems, Inc.
|
||
*
|
||
* See the file "license.terms" for information on usage and redistribution
|
||
* of this file, and for a DISCLAIMER OF ALL WARRANTIES.
|
||
*
|
||
* SCCS: @(#) tclMain.c 1.54 97/08/07 19:04:43
|
||
*/
|
||
|
||
#include "tcl.h"
|
||
#include "tclInt.h"
|
||
|
||
/*
|
||
* The following code ensures that tclLink.c is linked whenever
|
||
* Tcl is linked. Without this code there's no reference to the
|
||
* code in that file from anywhere in Tcl, so it may not be
|
||
* linked into the application.
|
||
*/
|
||
|
||
EXTERN int Tcl_LinkVar();
|
||
int (*tclDummyLinkVarPtr)() = Tcl_LinkVar;
|
||
|
||
/*
|
||
* 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).
|
||
* Note: "exit" should really be declared here, but there's no way to
|
||
* declare it without causing conflicts with other definitions elsewher
|
||
* on some systems, so it's better just to leave it out.
|
||
*/
|
||
|
||
extern int isatty _ANSI_ARGS_((int fd));
|
||
extern char * strcpy _ANSI_ARGS_((char *dst, CONST char *src));
|
||
|
||
static Tcl_Interp *interp; /* Interpreter for application. */
|
||
|
||
#ifdef TCL_MEM_DEBUG
|
||
static char dumpFile[100]; /* Records where to dump memory allocation
|
||
* information. */
|
||
static int quitFlag = 0; /* 1 means "checkmem" command was called,
|
||
* so the application should quit and dump
|
||
* memory allocation information. */
|
||
#endif
|
||
|
||
/*
|
||
* Forward references for procedures defined later in this file:
|
||
*/
|
||
|
||
#ifdef TCL_MEM_DEBUG
|
||
static int CheckmemCmd _ANSI_ARGS_((ClientData clientData,
|
||
Tcl_Interp *interp, int argc, char *argv[]));
|
||
#endif
|
||
|
||
/*
|
||
*----------------------------------------------------------------------
|
||
*
|
||
* Tcl_Main --
|
||
*
|
||
* Main program for tclsh and most other Tcl-based applications.
|
||
*
|
||
* Results:
|
||
* None. This procedure never returns (it exits the process when
|
||
* it's done.
|
||
*
|
||
* Side effects:
|
||
* This procedure initializes the Tk 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;
|
||
/* Application-specific initialization
|
||
* procedure to call after most
|
||
* initialization but before starting to
|
||
* execute commands. */
|
||
{
|
||
Tcl_Obj *prompt1NamePtr = NULL;
|
||
Tcl_Obj *prompt2NamePtr = NULL;
|
||
Tcl_Obj *resultPtr;
|
||
Tcl_Obj *commandPtr = NULL;
|
||
char buffer[1000], *args, *fileName, *bytes;
|
||
int code, gotPartial, tty, length;
|
||
int exitCode = 0;
|
||
Tcl_Channel inChannel, outChannel, errChannel;
|
||
|
||
Tcl_FindExecutable(argv[0]);
|
||
interp = Tcl_CreateInterp();
|
||
#ifdef TCL_MEM_DEBUG
|
||
Tcl_InitMemory(interp);
|
||
Tcl_CreateCommand(interp, "checkmem", CheckmemCmd, (ClientData) 0,
|
||
(Tcl_CmdDeleteProc *) NULL);
|
||
#endif
|
||
|
||
/*
|
||
* 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.
|
||
*/
|
||
|
||
fileName = NULL;
|
||
if ((argc > 1) && (argv[1][0] != '-')) {
|
||
fileName = argv[1];
|
||
argc--;
|
||
argv++;
|
||
}
|
||
args = Tcl_Merge(argc-1, argv+1);
|
||
Tcl_SetVar(interp, "argv", args, TCL_GLOBAL_ONLY);
|
||
ckfree(args);
|
||
TclFormatInt(buffer, argc-1);
|
||
Tcl_SetVar(interp, "argc", buffer, TCL_GLOBAL_ONLY);
|
||
Tcl_SetVar(interp, "argv0", (fileName != NULL) ? fileName : argv[0],
|
||
TCL_GLOBAL_ONLY);
|
||
|
||
/*
|
||
* Set the "tcl_interactive" variable.
|
||
*/
|
||
|
||
tty = isatty(0);
|
||
Tcl_SetVar(interp, "tcl_interactive",
|
||
((fileName == NULL) && tty) ? "1" : "0", TCL_GLOBAL_ONLY);
|
||
|
||
/*
|
||
* Invoke application-specific initialization.
|
||
*/
|
||
|
||
if ((*appInitProc)(interp) != TCL_OK) {
|
||
errChannel = Tcl_GetStdChannel(TCL_STDERR);
|
||
if (errChannel) {
|
||
Tcl_Write(errChannel,
|
||
"application-specific initialization failed: ", -1);
|
||
Tcl_Write(errChannel, interp->result, -1);
|
||
Tcl_Write(errChannel, "\n", 1);
|
||
}
|
||
}
|
||
|
||
/*
|
||
* If a script file was specified then just source that file
|
||
* and quit.
|
||
*/
|
||
|
||
if (fileName != NULL) {
|
||
code = Tcl_EvalFile(interp, fileName);
|
||
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_Write(errChannel,
|
||
Tcl_GetVar(interp, "errorInfo", TCL_GLOBAL_ONLY), -1);
|
||
Tcl_Write(errChannel, "\n", 1);
|
||
}
|
||
exitCode = 1;
|
||
}
|
||
goto done;
|
||
}
|
||
|
||
/*
|
||
* We're running interactively. Source a user-specific startup
|
||
* file if the application specified one and if the file exists.
|
||
*/
|
||
|
||
Tcl_SourceRCFile(interp);
|
||
|
||
/*
|
||
* 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);
|
||
prompt1NamePtr = Tcl_NewStringObj("tcl_prompt1", -1);
|
||
Tcl_IncrRefCount(prompt1NamePtr);
|
||
prompt2NamePtr = Tcl_NewStringObj("tcl_prompt2", -1);
|
||
Tcl_IncrRefCount(prompt2NamePtr);
|
||
|
||
inChannel = Tcl_GetStdChannel(TCL_STDIN);
|
||
outChannel = Tcl_GetStdChannel(TCL_STDOUT);
|
||
gotPartial = 0;
|
||
while (1) {
|
||
if (tty) {
|
||
Tcl_Obj *promptCmdPtr;
|
||
|
||
promptCmdPtr = Tcl_ObjGetVar2(interp,
|
||
(gotPartial? prompt2NamePtr : prompt1NamePtr),
|
||
(Tcl_Obj *) NULL, TCL_GLOBAL_ONLY);
|
||
if (promptCmdPtr == NULL) {
|
||
defaultPrompt:
|
||
if (!gotPartial && outChannel) {
|
||
Tcl_Write(outChannel, "% ", 2);
|
||
}
|
||
} else {
|
||
code = Tcl_EvalObj(interp, promptCmdPtr);
|
||
inChannel = Tcl_GetStdChannel(TCL_STDIN);
|
||
outChannel = Tcl_GetStdChannel(TCL_STDOUT);
|
||
errChannel = Tcl_GetStdChannel(TCL_STDERR);
|
||
if (code != TCL_OK) {
|
||
if (errChannel) {
|
||
resultPtr = Tcl_GetObjResult(interp);
|
||
bytes = Tcl_GetStringFromObj(resultPtr, &length);
|
||
Tcl_Write(errChannel, bytes, length);
|
||
Tcl_Write(errChannel, "\n", 1);
|
||
}
|
||
Tcl_AddErrorInfo(interp,
|
||
"\n (script that generates prompt)");
|
||
goto defaultPrompt;
|
||
}
|
||
}
|
||
if (outChannel) {
|
||
Tcl_Flush(outChannel);
|
||
}
|
||
}
|
||
if (!inChannel) {
|
||
goto done;
|
||
}
|
||
length = Tcl_GetsObj(inChannel, commandPtr);
|
||
if (length < 0) {
|
||
goto done;
|
||
}
|
||
if ((length == 0) && Tcl_Eof(inChannel) && (!gotPartial)) {
|
||
goto done;
|
||
}
|
||
|
||
/*
|
||
* Add the newline removed by Tcl_GetsObj back to the string.
|
||
*/
|
||
|
||
Tcl_AppendToObj(commandPtr, "\n", 1);
|
||
if (!TclObjCommandComplete(commandPtr)) {
|
||
gotPartial = 1;
|
||
continue;
|
||
}
|
||
|
||
gotPartial = 0;
|
||
code = Tcl_RecordAndEvalObj(interp, commandPtr, 0);
|
||
inChannel = Tcl_GetStdChannel(TCL_STDIN);
|
||
outChannel = Tcl_GetStdChannel(TCL_STDOUT);
|
||
errChannel = Tcl_GetStdChannel(TCL_STDERR);
|
||
Tcl_SetObjLength(commandPtr, 0);
|
||
if (code != TCL_OK) {
|
||
if (errChannel) {
|
||
resultPtr = Tcl_GetObjResult(interp);
|
||
bytes = Tcl_GetStringFromObj(resultPtr, &length);
|
||
Tcl_Write(errChannel, bytes, length);
|
||
Tcl_Write(errChannel, "\n", 1);
|
||
}
|
||
} else if (tty) {
|
||
resultPtr = Tcl_GetObjResult(interp);
|
||
bytes = Tcl_GetStringFromObj(resultPtr, &length);
|
||
if ((length > 0) && outChannel) {
|
||
Tcl_Write(outChannel, bytes, length);
|
||
Tcl_Write(outChannel, "\n", 1);
|
||
}
|
||
}
|
||
#ifdef TCL_MEM_DEBUG
|
||
if (quitFlag) {
|
||
Tcl_DecrRefCount(commandPtr);
|
||
Tcl_DecrRefCount(prompt1NamePtr);
|
||
Tcl_DecrRefCount(prompt2NamePtr);
|
||
Tcl_DeleteInterp(interp);
|
||
Tcl_Exit(0);
|
||
}
|
||
#endif
|
||
}
|
||
|
||
/*
|
||
* 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.
|
||
*/
|
||
|
||
done:
|
||
if (commandPtr != NULL) {
|
||
Tcl_DecrRefCount(commandPtr);
|
||
}
|
||
if (prompt1NamePtr != NULL) {
|
||
Tcl_DecrRefCount(prompt1NamePtr);
|
||
}
|
||
if (prompt2NamePtr != NULL) {
|
||
Tcl_DecrRefCount(prompt2NamePtr);
|
||
}
|
||
sprintf(buffer, "exit %d", exitCode);
|
||
Tcl_Eval(interp, buffer);
|
||
}
|
||
|
||
/*
|
||
*----------------------------------------------------------------------
|
||
*
|
||
* CheckmemCmd --
|
||
*
|
||
* This is the command procedure for the "checkmem" command, which
|
||
* causes the application to exit after printing information about
|
||
* memory usage to the file passed to this command as its first
|
||
* argument.
|
||
*
|
||
* Results:
|
||
* Returns a standard Tcl completion code.
|
||
*
|
||
* Side effects:
|
||
* None.
|
||
*
|
||
*----------------------------------------------------------------------
|
||
*/
|
||
#ifdef TCL_MEM_DEBUG
|
||
|
||
/* ARGSUSED */
|
||
static int
|
||
CheckmemCmd(clientData, interp, argc, argv)
|
||
ClientData clientData; /* Not used. */
|
||
Tcl_Interp *interp; /* Interpreter for evaluation. */
|
||
int argc; /* Number of arguments. */
|
||
char *argv[]; /* String values of arguments. */
|
||
{
|
||
extern char *tclMemDumpFileName;
|
||
if (argc != 2) {
|
||
Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
|
||
" fileName\"", (char *) NULL);
|
||
return TCL_ERROR;
|
||
}
|
||
strcpy(dumpFile, argv[1]);
|
||
tclMemDumpFileName = dumpFile;
|
||
quitFlag = 1;
|
||
return TCL_OK;
|
||
}
|
||
#endif
|