OpenFPGA/libs/EXTERNAL/tcl8.6.12/generic/tclBasic.c

9272 lines
258 KiB
C
Raw Normal View History

2022-06-07 11:15:20 -05:00
/*
* tclBasic.c --
*
* Contains the basic facilities for TCL command interpretation,
* including interpreter creation and deletion, command creation and
* deletion, and command/script execution.
*
* Copyright (c) 1987-1994 The Regents of the University of California.
* Copyright (c) 1994-1997 Sun Microsystems, Inc.
* Copyright (c) 1998-1999 by Scriptics Corporation.
* Copyright (c) 2001, 2002 by Kevin B. Kenny. All rights reserved.
* Copyright (c) 2007 Daniel A. Steffen <das@users.sourceforge.net>
* Copyright (c) 2006-2008 by Joe Mistachkin. All rights reserved.
* Copyright (c) 2008 Miguel Sofer <msofer@users.sourceforge.net>
*
* See the file "license.terms" for information on usage and redistribution of
* this file, and for a DISCLAIMER OF ALL WARRANTIES.
*/
#include "tclInt.h"
#include "tclOOInt.h"
#include "tclCompile.h"
#include "tommath.h"
#include <math.h>
#include <assert.h>
#define INTERP_STACK_INITIAL_SIZE 2000
#define CORO_STACK_INITIAL_SIZE 200
/*
* Determine whether we're using IEEE floating point
*/
#if (FLT_RADIX == 2) && (DBL_MANT_DIG == 53) && (DBL_MAX_EXP == 1024)
# define IEEE_FLOATING_POINT
/* Largest odd integer that can be represented exactly in a double */
# define MAX_EXACT 9007199254740991.0
#endif
/*
* The following structure defines the client data for a math function
* registered with Tcl_CreateMathFunc
*/
typedef struct OldMathFuncData {
Tcl_MathProc *proc; /* Handler function */
int numArgs; /* Number of args expected */
Tcl_ValueType *argTypes; /* Types of the args */
ClientData clientData; /* Client data for the handler function */
} OldMathFuncData;
/*
* This is the script cancellation struct and hash table. The hash table is
* used to keep track of the information necessary to process script
* cancellation requests, including the original interp, asynchronous handler
* tokens (created by Tcl_AsyncCreate), and the clientData and flags arguments
* passed to Tcl_CancelEval on a per-interp basis. The cancelLock mutex is
* used for protecting calls to Tcl_CancelEval as well as protecting access to
* the hash table below.
*/
typedef struct {
Tcl_Interp *interp; /* Interp this struct belongs to. */
Tcl_AsyncHandler async; /* Async handler token for script
* cancellation. */
char *result; /* The script cancellation result or NULL for
* a default result. */
int length; /* Length of the above error message. */
ClientData clientData; /* Ignored */
int flags; /* Additional flags */
} CancelInfo;
static Tcl_HashTable cancelTable;
static int cancelTableInitialized = 0; /* 0 means not yet initialized. */
TCL_DECLARE_MUTEX(cancelLock)
/*
* Declarations for managing contexts for non-recursive coroutines. Contexts
* are used to save the evaluation state between NR calls to each coro.
*/
#define SAVE_CONTEXT(context) \
(context).framePtr = iPtr->framePtr; \
(context).varFramePtr = iPtr->varFramePtr; \
(context).cmdFramePtr = iPtr->cmdFramePtr; \
(context).lineLABCPtr = iPtr->lineLABCPtr
#define RESTORE_CONTEXT(context) \
iPtr->framePtr = (context).framePtr; \
iPtr->varFramePtr = (context).varFramePtr; \
iPtr->cmdFramePtr = (context).cmdFramePtr; \
iPtr->lineLABCPtr = (context).lineLABCPtr
/*
* Static functions in this file:
*/
static char * CallCommandTraces(Interp *iPtr, Command *cmdPtr,
const char *oldName, const char *newName,
int flags);
static int CancelEvalProc(ClientData clientData,
Tcl_Interp *interp, int code);
static int CheckDoubleResult(Tcl_Interp *interp, double dResult);
static void DeleteCoroutine(ClientData clientData);
static void DeleteInterpProc(Tcl_Interp *interp);
static void DeleteOpCmdClientData(ClientData clientData);
#ifdef USE_DTRACE
static Tcl_ObjCmdProc DTraceObjCmd;
static Tcl_NRPostProc DTraceCmdReturn;
#else
# define DTraceCmdReturn NULL
#endif /* USE_DTRACE */
static Tcl_ObjCmdProc ExprAbsFunc;
static Tcl_ObjCmdProc ExprBinaryFunc;
static Tcl_ObjCmdProc ExprBoolFunc;
static Tcl_ObjCmdProc ExprCeilFunc;
static Tcl_ObjCmdProc ExprDoubleFunc;
static Tcl_ObjCmdProc ExprEntierFunc;
static Tcl_ObjCmdProc ExprFloorFunc;
static Tcl_ObjCmdProc ExprIntFunc;
static Tcl_ObjCmdProc ExprIsqrtFunc;
static Tcl_ObjCmdProc ExprRandFunc;
static Tcl_ObjCmdProc ExprRoundFunc;
static Tcl_ObjCmdProc ExprSqrtFunc;
static Tcl_ObjCmdProc ExprSrandFunc;
static Tcl_ObjCmdProc ExprUnaryFunc;
static Tcl_ObjCmdProc ExprWideFunc;
static void MathFuncWrongNumArgs(Tcl_Interp *interp, int expected,
int actual, Tcl_Obj *const *objv);
static Tcl_NRPostProc NRCoroutineCallerCallback;
static Tcl_NRPostProc NRCoroutineExitCallback;
static Tcl_NRPostProc NRCommand;
static Tcl_ObjCmdProc OldMathFuncProc;
static void OldMathFuncDeleteProc(ClientData clientData);
static void ProcessUnexpectedResult(Tcl_Interp *interp,
int returnCode);
static int RewindCoroutine(CoroutineData *corPtr, int result);
static void TEOV_SwitchVarFrame(Tcl_Interp *interp);
static void TEOV_PushExceptionHandlers(Tcl_Interp *interp,
int objc, Tcl_Obj *const objv[], int flags);
static inline Command * TEOV_LookupCmdFromObj(Tcl_Interp *interp,
Tcl_Obj *namePtr, Namespace *lookupNsPtr);
static int TEOV_NotFound(Tcl_Interp *interp, int objc,
Tcl_Obj *const objv[], Namespace *lookupNsPtr);
static int TEOV_RunEnterTraces(Tcl_Interp *interp,
Command **cmdPtrPtr, Tcl_Obj *commandPtr, int objc,
Tcl_Obj *const objv[]);
static Tcl_NRPostProc RewindCoroutineCallback;
static Tcl_NRPostProc TEOEx_ByteCodeCallback;
static Tcl_NRPostProc TEOEx_ListCallback;
static Tcl_NRPostProc TEOV_Error;
static Tcl_NRPostProc TEOV_Exception;
static Tcl_NRPostProc TEOV_NotFoundCallback;
static Tcl_NRPostProc TEOV_RestoreVarFrame;
static Tcl_NRPostProc TEOV_RunLeaveTraces;
static Tcl_NRPostProc EvalObjvCore;
static Tcl_NRPostProc Dispatch;
static Tcl_ObjCmdProc NRCoroInjectObjCmd;
static Tcl_NRPostProc NRPostInvoke;
static Tcl_ObjCmdProc CoroTypeObjCmd;
MODULE_SCOPE const TclStubs tclStubs;
/*
* Magical counts for the number of arguments accepted by a coroutine command
* after particular kinds of [yield].
*/
#define CORO_ACTIVATE_YIELD PTR2INT(NULL)
#define CORO_ACTIVATE_YIELDM PTR2INT(NULL)+1
#define COROUTINE_ARGUMENTS_SINGLE_OPTIONAL (-1)
#define COROUTINE_ARGUMENTS_ARBITRARY (-2)
/*
* The following structure define the commands in the Tcl core.
*/
typedef struct {
const char *name; /* Name of object-based command. */
Tcl_ObjCmdProc *objProc; /* Object-based function for command. */
CompileProc *compileProc; /* Function called to compile command. */
Tcl_ObjCmdProc *nreProc; /* NR-based function for command */
int flags; /* Various flag bits, as defined below. */
} CmdInfo;
#define CMD_IS_SAFE 1 /* Whether this command is part of the set of
* commands present by default in a safe
* interpreter. */
/* CMD_COMPILES_EXPANDED - Whether the compiler for this command can handle
* expansion for itself rather than needing the generic layer to take care of
* it for it. Defined in tclInt.h. */
/*
* The built-in commands, and the functions that implement them:
*/
static const CmdInfo builtInCmds[] = {
/*
* Commands in the generic core.
*/
{"append", Tcl_AppendObjCmd, TclCompileAppendCmd, NULL, CMD_IS_SAFE},
{"apply", Tcl_ApplyObjCmd, NULL, TclNRApplyObjCmd, CMD_IS_SAFE},
{"break", Tcl_BreakObjCmd, TclCompileBreakCmd, NULL, CMD_IS_SAFE},
#ifndef EXCLUDE_OBSOLETE_COMMANDS
{"case", Tcl_CaseObjCmd, NULL, NULL, CMD_IS_SAFE},
#endif
{"catch", Tcl_CatchObjCmd, TclCompileCatchCmd, TclNRCatchObjCmd, CMD_IS_SAFE},
{"concat", Tcl_ConcatObjCmd, TclCompileConcatCmd, NULL, CMD_IS_SAFE},
{"continue", Tcl_ContinueObjCmd, TclCompileContinueCmd, NULL, CMD_IS_SAFE},
{"coroutine", NULL, NULL, TclNRCoroutineObjCmd, CMD_IS_SAFE},
{"error", Tcl_ErrorObjCmd, TclCompileErrorCmd, NULL, CMD_IS_SAFE},
{"eval", Tcl_EvalObjCmd, NULL, TclNREvalObjCmd, CMD_IS_SAFE},
{"expr", Tcl_ExprObjCmd, TclCompileExprCmd, TclNRExprObjCmd, CMD_IS_SAFE},
{"for", Tcl_ForObjCmd, TclCompileForCmd, TclNRForObjCmd, CMD_IS_SAFE},
{"foreach", Tcl_ForeachObjCmd, TclCompileForeachCmd, TclNRForeachCmd, CMD_IS_SAFE},
{"format", Tcl_FormatObjCmd, TclCompileFormatCmd, NULL, CMD_IS_SAFE},
{"global", Tcl_GlobalObjCmd, TclCompileGlobalCmd, NULL, CMD_IS_SAFE},
{"if", Tcl_IfObjCmd, TclCompileIfCmd, TclNRIfObjCmd, CMD_IS_SAFE},
{"incr", Tcl_IncrObjCmd, TclCompileIncrCmd, NULL, CMD_IS_SAFE},
{"join", Tcl_JoinObjCmd, NULL, NULL, CMD_IS_SAFE},
{"lappend", Tcl_LappendObjCmd, TclCompileLappendCmd, NULL, CMD_IS_SAFE},
{"lassign", Tcl_LassignObjCmd, TclCompileLassignCmd, NULL, CMD_IS_SAFE},
{"lindex", Tcl_LindexObjCmd, TclCompileLindexCmd, NULL, CMD_IS_SAFE},
{"linsert", Tcl_LinsertObjCmd, TclCompileLinsertCmd, NULL, CMD_IS_SAFE},
{"list", Tcl_ListObjCmd, TclCompileListCmd, NULL, CMD_IS_SAFE|CMD_COMPILES_EXPANDED},
{"llength", Tcl_LlengthObjCmd, TclCompileLlengthCmd, NULL, CMD_IS_SAFE},
{"lmap", Tcl_LmapObjCmd, TclCompileLmapCmd, TclNRLmapCmd, CMD_IS_SAFE},
{"lrange", Tcl_LrangeObjCmd, TclCompileLrangeCmd, NULL, CMD_IS_SAFE},
{"lrepeat", Tcl_LrepeatObjCmd, NULL, NULL, CMD_IS_SAFE},
{"lreplace", Tcl_LreplaceObjCmd, TclCompileLreplaceCmd, NULL, CMD_IS_SAFE},
{"lreverse", Tcl_LreverseObjCmd, NULL, NULL, CMD_IS_SAFE},
{"lsearch", Tcl_LsearchObjCmd, NULL, NULL, CMD_IS_SAFE},
{"lset", Tcl_LsetObjCmd, TclCompileLsetCmd, NULL, CMD_IS_SAFE},
{"lsort", Tcl_LsortObjCmd, NULL, NULL, CMD_IS_SAFE},
{"package", Tcl_PackageObjCmd, NULL, TclNRPackageObjCmd, CMD_IS_SAFE},
{"proc", Tcl_ProcObjCmd, NULL, NULL, CMD_IS_SAFE},
{"regexp", Tcl_RegexpObjCmd, TclCompileRegexpCmd, NULL, CMD_IS_SAFE},
{"regsub", Tcl_RegsubObjCmd, TclCompileRegsubCmd, NULL, CMD_IS_SAFE},
{"rename", Tcl_RenameObjCmd, NULL, NULL, CMD_IS_SAFE},
{"return", Tcl_ReturnObjCmd, TclCompileReturnCmd, NULL, CMD_IS_SAFE},
{"scan", Tcl_ScanObjCmd, NULL, NULL, CMD_IS_SAFE},
{"set", Tcl_SetObjCmd, TclCompileSetCmd, NULL, CMD_IS_SAFE},
{"split", Tcl_SplitObjCmd, NULL, NULL, CMD_IS_SAFE},
{"subst", Tcl_SubstObjCmd, TclCompileSubstCmd, TclNRSubstObjCmd, CMD_IS_SAFE},
{"switch", Tcl_SwitchObjCmd, TclCompileSwitchCmd, TclNRSwitchObjCmd, CMD_IS_SAFE},
{"tailcall", NULL, TclCompileTailcallCmd, TclNRTailcallObjCmd, CMD_IS_SAFE},
{"throw", Tcl_ThrowObjCmd, TclCompileThrowCmd, NULL, CMD_IS_SAFE},
{"trace", Tcl_TraceObjCmd, NULL, NULL, CMD_IS_SAFE},
{"try", Tcl_TryObjCmd, TclCompileTryCmd, TclNRTryObjCmd, CMD_IS_SAFE},
{"unset", Tcl_UnsetObjCmd, TclCompileUnsetCmd, NULL, CMD_IS_SAFE},
{"uplevel", Tcl_UplevelObjCmd, NULL, TclNRUplevelObjCmd, CMD_IS_SAFE},
{"upvar", Tcl_UpvarObjCmd, TclCompileUpvarCmd, NULL, CMD_IS_SAFE},
{"variable", Tcl_VariableObjCmd, TclCompileVariableCmd, NULL, CMD_IS_SAFE},
{"while", Tcl_WhileObjCmd, TclCompileWhileCmd, TclNRWhileObjCmd, CMD_IS_SAFE},
{"yield", NULL, TclCompileYieldCmd, TclNRYieldObjCmd, CMD_IS_SAFE},
{"yieldto", NULL, TclCompileYieldToCmd, TclNRYieldToObjCmd, CMD_IS_SAFE},
/*
* Commands in the OS-interface. Note that many of these are unsafe.
*/
{"after", Tcl_AfterObjCmd, NULL, NULL, CMD_IS_SAFE},
{"cd", Tcl_CdObjCmd, NULL, NULL, 0},
{"close", Tcl_CloseObjCmd, NULL, NULL, CMD_IS_SAFE},
{"eof", Tcl_EofObjCmd, NULL, NULL, CMD_IS_SAFE},
{"exec", Tcl_ExecObjCmd, NULL, NULL, 0},
{"exit", Tcl_ExitObjCmd, NULL, NULL, 0},
{"fblocked", Tcl_FblockedObjCmd, NULL, NULL, CMD_IS_SAFE},
{"fconfigure", Tcl_FconfigureObjCmd, NULL, NULL, 0},
{"fcopy", Tcl_FcopyObjCmd, NULL, NULL, CMD_IS_SAFE},
{"fileevent", Tcl_FileEventObjCmd, NULL, NULL, CMD_IS_SAFE},
{"flush", Tcl_FlushObjCmd, NULL, NULL, CMD_IS_SAFE},
{"gets", Tcl_GetsObjCmd, NULL, NULL, CMD_IS_SAFE},
{"glob", Tcl_GlobObjCmd, NULL, NULL, 0},
{"load", Tcl_LoadObjCmd, NULL, NULL, 0},
{"open", Tcl_OpenObjCmd, NULL, NULL, 0},
{"pid", Tcl_PidObjCmd, NULL, NULL, CMD_IS_SAFE},
{"puts", Tcl_PutsObjCmd, NULL, NULL, CMD_IS_SAFE},
{"pwd", Tcl_PwdObjCmd, NULL, NULL, 0},
{"read", Tcl_ReadObjCmd, NULL, NULL, CMD_IS_SAFE},
{"seek", Tcl_SeekObjCmd, NULL, NULL, CMD_IS_SAFE},
{"socket", Tcl_SocketObjCmd, NULL, NULL, 0},
{"source", Tcl_SourceObjCmd, NULL, TclNRSourceObjCmd, 0},
{"tell", Tcl_TellObjCmd, NULL, NULL, CMD_IS_SAFE},
{"time", Tcl_TimeObjCmd, NULL, NULL, CMD_IS_SAFE},
#ifdef TCL_TIMERATE
{"timerate", Tcl_TimeRateObjCmd, NULL, NULL, CMD_IS_SAFE},
#endif
{"unload", Tcl_UnloadObjCmd, NULL, NULL, 0},
{"update", Tcl_UpdateObjCmd, NULL, NULL, CMD_IS_SAFE},
{"vwait", Tcl_VwaitObjCmd, NULL, NULL, CMD_IS_SAFE},
{NULL, NULL, NULL, NULL, 0}
};
/*
* Math functions. All are safe.
*/
typedef struct {
const char *name; /* Name of the function. The full name is
* "::tcl::mathfunc::<name>". */
Tcl_ObjCmdProc *objCmdProc; /* Function that evaluates the function */
ClientData clientData; /* Client data for the function */
} BuiltinFuncDef;
static const BuiltinFuncDef BuiltinFuncTable[] = {
{ "abs", ExprAbsFunc, NULL },
{ "acos", ExprUnaryFunc, (ClientData) acos },
{ "asin", ExprUnaryFunc, (ClientData) asin },
{ "atan", ExprUnaryFunc, (ClientData) atan },
{ "atan2", ExprBinaryFunc, (ClientData) atan2 },
{ "bool", ExprBoolFunc, NULL },
{ "ceil", ExprCeilFunc, NULL },
{ "cos", ExprUnaryFunc, (ClientData) cos },
{ "cosh", ExprUnaryFunc, (ClientData) cosh },
{ "double", ExprDoubleFunc, NULL },
{ "entier", ExprEntierFunc, NULL },
{ "exp", ExprUnaryFunc, (ClientData) exp },
{ "floor", ExprFloorFunc, NULL },
{ "fmod", ExprBinaryFunc, (ClientData) fmod },
{ "hypot", ExprBinaryFunc, (ClientData) hypot },
{ "int", ExprIntFunc, NULL },
{ "isqrt", ExprIsqrtFunc, NULL },
{ "log", ExprUnaryFunc, (ClientData) log },
{ "log10", ExprUnaryFunc, (ClientData) log10 },
{ "pow", ExprBinaryFunc, (ClientData) pow },
{ "rand", ExprRandFunc, NULL },
{ "round", ExprRoundFunc, NULL },
{ "sin", ExprUnaryFunc, (ClientData) sin },
{ "sinh", ExprUnaryFunc, (ClientData) sinh },
{ "sqrt", ExprSqrtFunc, NULL },
{ "srand", ExprSrandFunc, NULL },
{ "tan", ExprUnaryFunc, (ClientData) tan },
{ "tanh", ExprUnaryFunc, (ClientData) tanh },
{ "wide", ExprWideFunc, NULL },
{ NULL, NULL, NULL }
};
/*
* TIP#174's math operators. All are safe.
*/
typedef struct {
const char *name; /* Name of object-based command. */
Tcl_ObjCmdProc *objProc; /* Object-based function for command. */
CompileProc *compileProc; /* Function called to compile command. */
union {
int numArgs;
int identity;
} i;
const char *expected; /* For error message, what argument(s)
* were expected. */
} OpCmdInfo;
static const OpCmdInfo mathOpCmds[] = {
{ "~", TclSingleOpCmd, TclCompileInvertOpCmd,
/* numArgs */ {1}, "integer"},
{ "!", TclSingleOpCmd, TclCompileNotOpCmd,
/* numArgs */ {1}, "boolean"},
{ "+", TclVariadicOpCmd, TclCompileAddOpCmd,
/* identity */ {0}, NULL},
{ "*", TclVariadicOpCmd, TclCompileMulOpCmd,
/* identity */ {1}, NULL},
{ "&", TclVariadicOpCmd, TclCompileAndOpCmd,
/* identity */ {-1}, NULL},
{ "|", TclVariadicOpCmd, TclCompileOrOpCmd,
/* identity */ {0}, NULL},
{ "^", TclVariadicOpCmd, TclCompileXorOpCmd,
/* identity */ {0}, NULL},
{ "**", TclVariadicOpCmd, TclCompilePowOpCmd,
/* identity */ {1}, NULL},
{ "<<", TclSingleOpCmd, TclCompileLshiftOpCmd,
/* numArgs */ {2}, "integer shift"},
{ ">>", TclSingleOpCmd, TclCompileRshiftOpCmd,
/* numArgs */ {2}, "integer shift"},
{ "%", TclSingleOpCmd, TclCompileModOpCmd,
/* numArgs */ {2}, "integer integer"},
{ "!=", TclSingleOpCmd, TclCompileNeqOpCmd,
/* numArgs */ {2}, "value value"},
{ "ne", TclSingleOpCmd, TclCompileStrneqOpCmd,
/* numArgs */ {2}, "value value"},
{ "in", TclSingleOpCmd, TclCompileInOpCmd,
/* numArgs */ {2}, "value list"},
{ "ni", TclSingleOpCmd, TclCompileNiOpCmd,
/* numArgs */ {2}, "value list"},
{ "-", TclNoIdentOpCmd, TclCompileMinusOpCmd,
/* unused */ {0}, "value ?value ...?"},
{ "/", TclNoIdentOpCmd, TclCompileDivOpCmd,
/* unused */ {0}, "value ?value ...?"},
{ "<", TclSortingOpCmd, TclCompileLessOpCmd,
/* unused */ {0}, NULL},
{ "<=", TclSortingOpCmd, TclCompileLeqOpCmd,
/* unused */ {0}, NULL},
{ ">", TclSortingOpCmd, TclCompileGreaterOpCmd,
/* unused */ {0}, NULL},
{ ">=", TclSortingOpCmd, TclCompileGeqOpCmd,
/* unused */ {0}, NULL},
{ "==", TclSortingOpCmd, TclCompileEqOpCmd,
/* unused */ {0}, NULL},
{ "eq", TclSortingOpCmd, TclCompileStreqOpCmd,
/* unused */ {0}, NULL},
{ NULL, NULL, NULL,
{0}, NULL}
};
/*
*----------------------------------------------------------------------
*
* TclFinalizeEvaluation --
*
* Finalizes the script cancellation hash table.
*
* Results:
* None.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
void
TclFinalizeEvaluation(void)
{
Tcl_MutexLock(&cancelLock);
if (cancelTableInitialized == 1) {
Tcl_DeleteHashTable(&cancelTable);
cancelTableInitialized = 0;
}
Tcl_MutexUnlock(&cancelLock);
}
/*
*----------------------------------------------------------------------
*
* Tcl_CreateInterp --
*
* Create a new TCL command interpreter.
*
* Results:
* The return value is a token for the interpreter, which may be used in
* calls to functions like Tcl_CreateCmd, Tcl_Eval, or Tcl_DeleteInterp.
*
* Side effects:
* The command interpreter is initialized with the built-in commands and
* with the variables documented in tclvars(n).
*
*----------------------------------------------------------------------
*/
Tcl_Interp *
Tcl_CreateInterp(void)
{
Interp *iPtr;
Tcl_Interp *interp;
Command *cmdPtr;
const BuiltinFuncDef *builtinFuncPtr;
const OpCmdInfo *opcmdInfoPtr;
const CmdInfo *cmdInfoPtr;
Tcl_Namespace *nsPtr;
Tcl_HashEntry *hPtr;
int isNew;
CancelInfo *cancelInfo;
union {
char c[sizeof(short)];
short s;
} order;
#ifdef TCL_COMPILE_STATS
ByteCodeStats *statsPtr;
#endif /* TCL_COMPILE_STATS */
char mathFuncName[32];
CallFrame *framePtr;
TclInitSubsystems();
/*
* Panic if someone updated the CallFrame structure without also updating
* the Tcl_CallFrame structure (or vice versa).
*/
if (sizeof(Tcl_CallFrame) < sizeof(CallFrame)) {
/*NOTREACHED*/
Tcl_Panic("Tcl_CallFrame must not be smaller than CallFrame");
}
#if defined(_WIN32) && !defined(_WIN64) && !defined(_USE_64BIT_TIME_T)
/* If Tcl is compiled on Win32 using -D_USE_64BIT_TIME_T
* the result is a binary incompatible with the 'standard' build of
* Tcl: All extensions using Tcl_StatBuf need to be recompiled in
* the same way. Therefore, this is not officially supported.
* In stead, it is recommended to use Win64 or Tcl 9.0 (not released yet)
*/
if ((TclOffset(Tcl_StatBuf,st_atime) != 32)
|| (TclOffset(Tcl_StatBuf,st_ctime) != 40)) {
/*NOTREACHED*/
Tcl_Panic("<sys/stat.h> is not compatible with MSVC");
}
#endif
if (cancelTableInitialized == 0) {
Tcl_MutexLock(&cancelLock);
if (cancelTableInitialized == 0) {
Tcl_InitHashTable(&cancelTable, TCL_ONE_WORD_KEYS);
cancelTableInitialized = 1;
}
Tcl_MutexUnlock(&cancelLock);
}
/*
* Initialize support for namespaces and create the global namespace
* (whose name is ""; an alias is "::"). This also initializes the Tcl
* object type table and other object management code.
*/
iPtr = ckalloc(sizeof(Interp));
interp = (Tcl_Interp *) iPtr;
iPtr->result = iPtr->resultSpace;
iPtr->freeProc = NULL;
iPtr->errorLine = 0;
iPtr->objResultPtr = Tcl_NewObj();
Tcl_IncrRefCount(iPtr->objResultPtr);
iPtr->handle = TclHandleCreate(iPtr);
iPtr->globalNsPtr = NULL;
iPtr->hiddenCmdTablePtr = NULL;
iPtr->interpInfo = NULL;
TCL_CT_ASSERT(sizeof(iPtr->extra) <= sizeof(Tcl_HashTable));
iPtr->extra.optimizer = TclOptimizeBytecode;
iPtr->numLevels = 0;
iPtr->maxNestingDepth = MAX_NESTING_DEPTH;
iPtr->framePtr = NULL; /* Initialise as soon as :: is available */
iPtr->varFramePtr = NULL; /* Initialise as soon as :: is available */
/*
* TIP #280 - Initialize the arrays used to extend the ByteCode and Proc
* structures.
*/
iPtr->cmdFramePtr = NULL;
iPtr->linePBodyPtr = ckalloc(sizeof(Tcl_HashTable));
iPtr->lineBCPtr = ckalloc(sizeof(Tcl_HashTable));
iPtr->lineLAPtr = ckalloc(sizeof(Tcl_HashTable));
iPtr->lineLABCPtr = ckalloc(sizeof(Tcl_HashTable));
Tcl_InitHashTable(iPtr->linePBodyPtr, TCL_ONE_WORD_KEYS);
Tcl_InitHashTable(iPtr->lineBCPtr, TCL_ONE_WORD_KEYS);
Tcl_InitHashTable(iPtr->lineLAPtr, TCL_ONE_WORD_KEYS);
Tcl_InitHashTable(iPtr->lineLABCPtr, TCL_ONE_WORD_KEYS);
iPtr->scriptCLLocPtr = NULL;
iPtr->activeVarTracePtr = NULL;
iPtr->returnOpts = NULL;
iPtr->errorInfo = NULL;
TclNewLiteralStringObj(iPtr->eiVar, "::errorInfo");
Tcl_IncrRefCount(iPtr->eiVar);
iPtr->errorStack = Tcl_NewListObj(0, NULL);
Tcl_IncrRefCount(iPtr->errorStack);
iPtr->resetErrorStack = 1;
TclNewLiteralStringObj(iPtr->upLiteral,"UP");
Tcl_IncrRefCount(iPtr->upLiteral);
TclNewLiteralStringObj(iPtr->callLiteral,"CALL");
Tcl_IncrRefCount(iPtr->callLiteral);
TclNewLiteralStringObj(iPtr->innerLiteral,"INNER");
Tcl_IncrRefCount(iPtr->innerLiteral);
iPtr->innerContext = Tcl_NewListObj(0, NULL);
Tcl_IncrRefCount(iPtr->innerContext);
iPtr->errorCode = NULL;
TclNewLiteralStringObj(iPtr->ecVar, "::errorCode");
Tcl_IncrRefCount(iPtr->ecVar);
iPtr->returnLevel = 1;
iPtr->returnCode = TCL_OK;
iPtr->rootFramePtr = NULL; /* Initialise as soon as :: is available */
iPtr->lookupNsPtr = NULL;
iPtr->appendResult = NULL;
iPtr->appendAvl = 0;
iPtr->appendUsed = 0;
Tcl_InitHashTable(&iPtr->packageTable, TCL_STRING_KEYS);
iPtr->packageUnknown = NULL;
#ifdef _WIN32
# define getenv(x) _wgetenv(L##x) /* On Windows, use _wgetenv below */
#endif
/* TIP #268 */
if (getenv("TCL_PKG_PREFER_LATEST") == NULL) {
iPtr->packagePrefer = PKG_PREFER_STABLE;
} else {
iPtr->packagePrefer = PKG_PREFER_LATEST;
}
iPtr->cmdCount = 0;
TclInitLiteralTable(&iPtr->literalTable);
iPtr->compileEpoch = 0;
iPtr->compiledProcPtr = NULL;
iPtr->resolverPtr = NULL;
iPtr->evalFlags = 0;
iPtr->scriptFile = NULL;
iPtr->flags = 0;
iPtr->tracePtr = NULL;
iPtr->tracesForbiddingInline = 0;
iPtr->activeCmdTracePtr = NULL;
iPtr->activeInterpTracePtr = NULL;
iPtr->assocData = NULL;
iPtr->execEnvPtr = NULL; /* Set after namespaces initialized. */
iPtr->emptyObjPtr = Tcl_NewObj();
/* Another empty object. */
Tcl_IncrRefCount(iPtr->emptyObjPtr);
iPtr->resultSpace[0] = 0;
iPtr->threadId = Tcl_GetCurrentThread();
/* TIP #378 */
#ifdef TCL_INTERP_DEBUG_FRAME
iPtr->flags |= INTERP_DEBUG_FRAME;
#else
if (getenv("TCL_INTERP_DEBUG_FRAME") != NULL) {
iPtr->flags |= INTERP_DEBUG_FRAME;
}
#endif
/*
* Initialise the tables for variable traces and searches *before*
* creating the global ns - so that the trace on errorInfo can be
* recorded.
*/
Tcl_InitHashTable(&iPtr->varTraces, TCL_ONE_WORD_KEYS);
Tcl_InitHashTable(&iPtr->varSearches, TCL_ONE_WORD_KEYS);
iPtr->globalNsPtr = NULL; /* Force creation of global ns below. */
iPtr->globalNsPtr = (Namespace *) Tcl_CreateNamespace(interp, "",
NULL, NULL);
if (iPtr->globalNsPtr == NULL) {
Tcl_Panic("Tcl_CreateInterp: can't create global namespace");
}
/*
* Initialise the rootCallframe. It cannot be allocated on the stack, as
* it has to be in place before TclCreateExecEnv tries to use a variable.
*/
/* This is needed to satisfy GCC 3.3's strict aliasing rules */
framePtr = ckalloc(sizeof(CallFrame));
(void) Tcl_PushCallFrame(interp, (Tcl_CallFrame *) framePtr,
(Tcl_Namespace *) iPtr->globalNsPtr, /*isProcCallFrame*/ 0);
framePtr->objc = 0;
iPtr->framePtr = framePtr;
iPtr->varFramePtr = framePtr;
iPtr->rootFramePtr = framePtr;
/*
* Initialize support for code compilation and execution. We call
* TclCreateExecEnv after initializing namespaces since it tries to
* reference a Tcl variable (it links to the Tcl "tcl_traceExec"
* variable).
*/
iPtr->execEnvPtr = TclCreateExecEnv(interp, INTERP_STACK_INITIAL_SIZE);
/*
* TIP #219, Tcl Channel Reflection API support.
*/
iPtr->chanMsg = NULL;
/*
* TIP #285, Script cancellation support.
*/
iPtr->asyncCancelMsg = Tcl_NewObj();
cancelInfo = ckalloc(sizeof(CancelInfo));
cancelInfo->interp = interp;
iPtr->asyncCancel = Tcl_AsyncCreate(CancelEvalProc, cancelInfo);
cancelInfo->async = iPtr->asyncCancel;
cancelInfo->result = NULL;
cancelInfo->length = 0;
Tcl_MutexLock(&cancelLock);
hPtr = Tcl_CreateHashEntry(&cancelTable, iPtr, &isNew);
Tcl_SetHashValue(hPtr, cancelInfo);
Tcl_MutexUnlock(&cancelLock);
/*
* Initialize the compilation and execution statistics kept for this
* interpreter.
*/
#ifdef TCL_COMPILE_STATS
statsPtr = &iPtr->stats;
statsPtr->numExecutions = 0;
statsPtr->numCompilations = 0;
statsPtr->numByteCodesFreed = 0;
memset(statsPtr->instructionCount, 0,
sizeof(statsPtr->instructionCount));
statsPtr->totalSrcBytes = 0.0;
statsPtr->totalByteCodeBytes = 0.0;
statsPtr->currentSrcBytes = 0.0;
statsPtr->currentByteCodeBytes = 0.0;
memset(statsPtr->srcCount, 0, sizeof(statsPtr->srcCount));
memset(statsPtr->byteCodeCount, 0, sizeof(statsPtr->byteCodeCount));
memset(statsPtr->lifetimeCount, 0, sizeof(statsPtr->lifetimeCount));
statsPtr->currentInstBytes = 0.0;
statsPtr->currentLitBytes = 0.0;
statsPtr->currentExceptBytes = 0.0;
statsPtr->currentAuxBytes = 0.0;
statsPtr->currentCmdMapBytes = 0.0;
statsPtr->numLiteralsCreated = 0;
statsPtr->totalLitStringBytes = 0.0;
statsPtr->currentLitStringBytes = 0.0;
memset(statsPtr->literalCount, 0, sizeof(statsPtr->literalCount));
#endif /* TCL_COMPILE_STATS */
/*
* Initialise the stub table pointer.
*/
iPtr->stubTable = &tclStubs;
/*
* Initialize the ensemble error message rewriting support.
*/
TclResetRewriteEnsemble(interp, 1);
/*
* TIP#143: Initialise the resource limit support.
*/
TclInitLimitSupport(interp);
/*
* Initialise the thread-specific data ekeko. Note that the thread's alloc
* cache was already initialised by the call to alloc the interp struct.
*/
#if defined(TCL_THREADS) && defined(USE_THREAD_ALLOC)
iPtr->allocCache = TclpGetAllocCache();
#else
iPtr->allocCache = NULL;
#endif
iPtr->pendingObjDataPtr = NULL;
iPtr->asyncReadyPtr = TclGetAsyncReadyPtr();
iPtr->deferredCallbacks = NULL;
/*
* Create the core commands. Do it here, rather than calling
* Tcl_CreateCommand, because it's faster (there's no need to check for a
* pre-existing command by the same name). If a command has a Tcl_CmdProc
* but no Tcl_ObjCmdProc, set the Tcl_ObjCmdProc to
* TclInvokeStringCommand. This is an object-based wrapper function that
* extracts strings, calls the string function, and creates an object for
* the result. Similarly, if a command has a Tcl_ObjCmdProc but no
* Tcl_CmdProc, set the Tcl_CmdProc to TclInvokeObjectCommand.
*/
for (cmdInfoPtr = builtInCmds; cmdInfoPtr->name != NULL; cmdInfoPtr++) {
if ((cmdInfoPtr->objProc == NULL)
&& (cmdInfoPtr->compileProc == NULL)
&& (cmdInfoPtr->nreProc == NULL)) {
Tcl_Panic("builtin command with NULL object command proc and a NULL compile proc");
}
hPtr = Tcl_CreateHashEntry(&iPtr->globalNsPtr->cmdTable,
cmdInfoPtr->name, &isNew);
if (isNew) {
cmdPtr = ckalloc(sizeof(Command));
cmdPtr->hPtr = hPtr;
cmdPtr->nsPtr = iPtr->globalNsPtr;
cmdPtr->refCount = 1;
cmdPtr->cmdEpoch = 0;
cmdPtr->compileProc = cmdInfoPtr->compileProc;
cmdPtr->proc = TclInvokeObjectCommand;
cmdPtr->clientData = cmdPtr;
cmdPtr->objProc = cmdInfoPtr->objProc;
cmdPtr->objClientData = NULL;
cmdPtr->deleteProc = NULL;
cmdPtr->deleteData = NULL;
cmdPtr->flags = 0;
if (cmdInfoPtr->flags & CMD_COMPILES_EXPANDED) {
cmdPtr->flags |= CMD_COMPILES_EXPANDED;
}
cmdPtr->importRefPtr = NULL;
cmdPtr->tracePtr = NULL;
cmdPtr->nreProc = cmdInfoPtr->nreProc;
Tcl_SetHashValue(hPtr, cmdPtr);
}
}
/*
* Create the "array", "binary", "chan", "clock", "dict", "encoding",
* "file", "info", "namespace" and "string" ensembles. Note that all these
* commands (and their subcommands that are not present in the global
* namespace) are wholly safe *except* for "clock", "encoding" and "file".
*/
TclInitArrayCmd(interp);
TclInitBinaryCmd(interp);
TclInitChanCmd(interp);
TclInitDictCmd(interp);
TclInitEncodingCmd(interp);
TclInitFileCmd(interp);
TclInitInfoCmd(interp);
TclInitNamespaceCmd(interp);
TclInitStringCmd(interp);
TclInitPrefixCmd(interp);
/*
* Register "clock" subcommands. These *do* go through
* Tcl_CreateObjCommand, since they aren't in the global namespace and
* involve ensembles.
*/
TclClockInit(interp);
/*
* Register the built-in functions. This is empty now that they are
* implemented as commands in the ::tcl::mathfunc namespace.
*/
/*
* Register the default [interp bgerror] handler.
*/
Tcl_CreateObjCommand(interp, "::tcl::Bgerror",
TclDefaultBgErrorHandlerObjCmd, NULL, NULL);
/*
* Create unsupported commands for debugging bytecode and objects.
*/
Tcl_CreateObjCommand(interp, "::tcl::unsupported::disassemble",
Tcl_DisassembleObjCmd, INT2PTR(0), NULL);
Tcl_CreateObjCommand(interp, "::tcl::unsupported::getbytecode",
Tcl_DisassembleObjCmd, INT2PTR(1), NULL);
Tcl_CreateObjCommand(interp, "::tcl::unsupported::representation",
Tcl_RepresentationCmd, NULL, NULL);
/* Adding the bytecode assembler command */
cmdPtr = (Command *) Tcl_NRCreateCommand(interp,
"::tcl::unsupported::assemble", Tcl_AssembleObjCmd,
TclNRAssembleObjCmd, NULL, NULL);
cmdPtr->compileProc = &TclCompileAssembleCmd;
/* Coroutine monkeybusiness */
Tcl_NRCreateCommand(interp, "::tcl::unsupported::inject", NULL,
NRCoroInjectObjCmd, NULL, NULL);
Tcl_CreateObjCommand(interp, "::tcl::unsupported::corotype",
CoroTypeObjCmd, NULL, NULL);
/* Create an unsupported command for timerate */
Tcl_CreateObjCommand(interp, "::tcl::unsupported::timerate",
Tcl_TimeRateObjCmd, NULL, NULL);
/* Export unsupported commands */
nsPtr = Tcl_FindNamespace(interp, "::tcl::unsupported", NULL, 0);
if (nsPtr) {
Tcl_Export(interp, nsPtr, "*", 1);
}
#ifdef USE_DTRACE
/*
* Register the tcl::dtrace command.
*/
Tcl_CreateObjCommand(interp, "::tcl::dtrace", DTraceObjCmd, NULL, NULL);
#endif /* USE_DTRACE */
/*
* Register the builtin math functions.
*/
nsPtr = Tcl_CreateNamespace(interp, "::tcl::mathfunc", NULL,NULL);
if (nsPtr == NULL) {
Tcl_Panic("Can't create math function namespace");
}
#define MATH_FUNC_PREFIX_LEN 17 /* == strlen("::tcl::mathfunc::") */
memcpy(mathFuncName, "::tcl::mathfunc::", MATH_FUNC_PREFIX_LEN);
for (builtinFuncPtr = BuiltinFuncTable; builtinFuncPtr->name != NULL;
builtinFuncPtr++) {
strcpy(mathFuncName+MATH_FUNC_PREFIX_LEN, builtinFuncPtr->name);
Tcl_CreateObjCommand(interp, mathFuncName,
builtinFuncPtr->objCmdProc, builtinFuncPtr->clientData, NULL);
Tcl_Export(interp, nsPtr, builtinFuncPtr->name, 0);
}
/*
* Register the mathematical "operator" commands. [TIP #174]
*/
nsPtr = Tcl_CreateNamespace(interp, "::tcl::mathop", NULL, NULL);
if (nsPtr == NULL) {
Tcl_Panic("can't create math operator namespace");
}
Tcl_Export(interp, nsPtr, "*", 1);
#define MATH_OP_PREFIX_LEN 15 /* == strlen("::tcl::mathop::") */
memcpy(mathFuncName, "::tcl::mathop::", MATH_OP_PREFIX_LEN);
for (opcmdInfoPtr=mathOpCmds ; opcmdInfoPtr->name!=NULL ; opcmdInfoPtr++){
TclOpCmdClientData *occdPtr = ckalloc(sizeof(TclOpCmdClientData));
occdPtr->op = opcmdInfoPtr->name;
occdPtr->i.numArgs = opcmdInfoPtr->i.numArgs;
occdPtr->expected = opcmdInfoPtr->expected;
strcpy(mathFuncName + MATH_OP_PREFIX_LEN, opcmdInfoPtr->name);
cmdPtr = (Command *) Tcl_CreateObjCommand(interp, mathFuncName,
opcmdInfoPtr->objProc, occdPtr, DeleteOpCmdClientData);
if (cmdPtr == NULL) {
Tcl_Panic("failed to create math operator %s",
opcmdInfoPtr->name);
} else if (opcmdInfoPtr->compileProc != NULL) {
cmdPtr->compileProc = opcmdInfoPtr->compileProc;
}
}
/*
* Do Multiple/Safe Interps Tcl init stuff
*/
TclInterpInit(interp);
TclSetupEnv(interp);
/*
* TIP #59: Make embedded configuration information available.
*/
TclInitEmbeddedConfigurationInformation(interp);
/*
* TIP #440: Declare the name of the script engine to be "Tcl".
*/
Tcl_SetVar2(interp, "tcl_platform", "engine", "Tcl",
TCL_GLOBAL_ONLY);
/*
* Compute the byte order of this machine.
*/
order.s = 1;
Tcl_SetVar2(interp, "tcl_platform", "byteOrder",
((order.c[0] == 1) ? "littleEndian" : "bigEndian"),
TCL_GLOBAL_ONLY);
Tcl_SetVar2Ex(interp, "tcl_platform", "wordSize",
Tcl_NewLongObj((long) sizeof(long)), TCL_GLOBAL_ONLY);
/* TIP #291 */
Tcl_SetVar2Ex(interp, "tcl_platform", "pointerSize",
Tcl_NewLongObj((long) sizeof(void *)), TCL_GLOBAL_ONLY);
/*
* Set up other variables such as tcl_version and tcl_library
*/
Tcl_SetVar(interp, "tcl_patchLevel", TCL_PATCH_LEVEL, TCL_GLOBAL_ONLY);
Tcl_SetVar(interp, "tcl_version", TCL_VERSION, TCL_GLOBAL_ONLY);
Tcl_TraceVar2(interp, "tcl_precision", NULL,
TCL_GLOBAL_ONLY|TCL_TRACE_READS|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
TclPrecTraceProc, NULL);
TclpSetVariables(interp);
#ifdef TCL_THREADS
/*
* The existence of the "threaded" element of the tcl_platform array
* indicates that this particular Tcl shell has been compiled with threads
* turned on. Using "info exists tcl_platform(threaded)" a Tcl script can
* introspect on the interpreter level of thread safety.
*/
Tcl_SetVar2(interp, "tcl_platform", "threaded", "1", TCL_GLOBAL_ONLY);
#endif
/*
* Register Tcl's version number.
* TIP #268: Full patchlevel instead of just major.minor
*/
Tcl_PkgProvideEx(interp, "Tcl", TCL_PATCH_LEVEL, &tclStubs);
if (TclTommath_Init(interp) != TCL_OK) {
Tcl_Panic("%s", Tcl_GetString(Tcl_GetObjResult(interp)));
}
if (TclOOInit(interp) != TCL_OK) {
Tcl_Panic("%s", Tcl_GetString(Tcl_GetObjResult(interp)));
}
/*
* Only build in zlib support if we've successfully detected a library to
* compile and link against.
*/
#ifdef HAVE_ZLIB
if (TclZlibInit(interp) != TCL_OK) {
Tcl_Panic("%s", Tcl_GetString(Tcl_GetObjResult(interp)));
}
#endif
TOP_CB(iPtr) = NULL;
return interp;
}
static void
DeleteOpCmdClientData(
ClientData clientData)
{
TclOpCmdClientData *occdPtr = clientData;
ckfree(occdPtr);
}
/*
*----------------------------------------------------------------------
*
* TclHideUnsafeCommands --
*
* Hides base commands that are not marked as safe from this interpreter.
*
* Results:
* TCL_OK if it succeeds, TCL_ERROR else.
*
* Side effects:
* Hides functionality in an interpreter.
*
*----------------------------------------------------------------------
*/
int
TclHideUnsafeCommands(
Tcl_Interp *interp) /* Hide commands in this interpreter. */
{
const CmdInfo *cmdInfoPtr;
if (interp == NULL) {
return TCL_ERROR;
}
for (cmdInfoPtr = builtInCmds; cmdInfoPtr->name != NULL; cmdInfoPtr++) {
if (!(cmdInfoPtr->flags & CMD_IS_SAFE)) {
Tcl_HideCommand(interp, cmdInfoPtr->name, cmdInfoPtr->name);
}
}
TclMakeEncodingCommandSafe(interp); /* Ugh! */
TclMakeFileCommandSafe(interp); /* Ugh! */
return TCL_OK;
}
/*
*--------------------------------------------------------------
*
* Tcl_CallWhenDeleted --
*
* Arrange for a function to be called before a given interpreter is
* deleted. The function is called as soon as Tcl_DeleteInterp is called;
* if Tcl_CallWhenDeleted is called on an interpreter that has already
* been deleted, the function will be called when the last Tcl_Release is
* done on the interpreter.
*
* Results:
* None.
*
* Side effects:
* When Tcl_DeleteInterp is invoked to delete interp, proc will be
* invoked. See the manual entry for details.
*
*--------------------------------------------------------------
*/
void
Tcl_CallWhenDeleted(
Tcl_Interp *interp, /* Interpreter to watch. */
Tcl_InterpDeleteProc *proc, /* Function to call when interpreter is about
* to be deleted. */
ClientData clientData) /* One-word value to pass to proc. */
{
Interp *iPtr = (Interp *) interp;
static Tcl_ThreadDataKey assocDataCounterKey;
int *assocDataCounterPtr =
Tcl_GetThreadData(&assocDataCounterKey, (int)sizeof(int));
int isNew;
char buffer[32 + TCL_INTEGER_SPACE];
AssocData *dPtr = ckalloc(sizeof(AssocData));
Tcl_HashEntry *hPtr;
sprintf(buffer, "Assoc Data Key #%d", *assocDataCounterPtr);
(*assocDataCounterPtr)++;
if (iPtr->assocData == NULL) {
iPtr->assocData = ckalloc(sizeof(Tcl_HashTable));
Tcl_InitHashTable(iPtr->assocData, TCL_STRING_KEYS);
}
hPtr = Tcl_CreateHashEntry(iPtr->assocData, buffer, &isNew);
dPtr->proc = proc;
dPtr->clientData = clientData;
Tcl_SetHashValue(hPtr, dPtr);
}
/*
*--------------------------------------------------------------
*
* Tcl_DontCallWhenDeleted --
*
* Cancel the arrangement for a function to be called when a given
* interpreter is deleted.
*
* Results:
* None.
*
* Side effects:
* If proc and clientData were previously registered as a callback via
* Tcl_CallWhenDeleted, they are unregistered. If they weren't previously
* registered then nothing happens.
*
*--------------------------------------------------------------
*/
void
Tcl_DontCallWhenDeleted(
Tcl_Interp *interp, /* Interpreter to watch. */
Tcl_InterpDeleteProc *proc, /* Function to call when interpreter is about
* to be deleted. */
ClientData clientData) /* One-word value to pass to proc. */
{
Interp *iPtr = (Interp *) interp;
Tcl_HashTable *hTablePtr;
Tcl_HashSearch hSearch;
Tcl_HashEntry *hPtr;
AssocData *dPtr;
hTablePtr = iPtr->assocData;
if (hTablePtr == NULL) {
return;
}
for (hPtr = Tcl_FirstHashEntry(hTablePtr, &hSearch); hPtr != NULL;
hPtr = Tcl_NextHashEntry(&hSearch)) {
dPtr = Tcl_GetHashValue(hPtr);
if ((dPtr->proc == proc) && (dPtr->clientData == clientData)) {
ckfree(dPtr);
Tcl_DeleteHashEntry(hPtr);
return;
}
}
}
/*
*----------------------------------------------------------------------
*
* Tcl_SetAssocData --
*
* Creates a named association between user-specified data, a delete
* function and this interpreter. If the association already exists the
* data is overwritten with the new data. The delete function will be
* invoked when the interpreter is deleted.
*
* Results:
* None.
*
* Side effects:
* Sets the associated data, creates the association if needed.
*
*----------------------------------------------------------------------
*/
void
Tcl_SetAssocData(
Tcl_Interp *interp, /* Interpreter to associate with. */
const char *name, /* Name for association. */
Tcl_InterpDeleteProc *proc, /* Proc to call when interpreter is about to
* be deleted. */
ClientData clientData) /* One-word value to pass to proc. */
{
Interp *iPtr = (Interp *) interp;
AssocData *dPtr;
Tcl_HashEntry *hPtr;
int isNew;
if (iPtr->assocData == NULL) {
iPtr->assocData = ckalloc(sizeof(Tcl_HashTable));
Tcl_InitHashTable(iPtr->assocData, TCL_STRING_KEYS);
}
hPtr = Tcl_CreateHashEntry(iPtr->assocData, name, &isNew);
if (isNew == 0) {
dPtr = Tcl_GetHashValue(hPtr);
} else {
dPtr = ckalloc(sizeof(AssocData));
}
dPtr->proc = proc;
dPtr->clientData = clientData;
Tcl_SetHashValue(hPtr, dPtr);
}
/*
*----------------------------------------------------------------------
*
* Tcl_DeleteAssocData --
*
* Deletes a named association of user-specified data with the specified
* interpreter.
*
* Results:
* None.
*
* Side effects:
* Deletes the association.
*
*----------------------------------------------------------------------
*/
void
Tcl_DeleteAssocData(
Tcl_Interp *interp, /* Interpreter to associate with. */
const char *name) /* Name of association. */
{
Interp *iPtr = (Interp *) interp;
AssocData *dPtr;
Tcl_HashEntry *hPtr;
if (iPtr->assocData == NULL) {
return;
}
hPtr = Tcl_FindHashEntry(iPtr->assocData, name);
if (hPtr == NULL) {
return;
}
dPtr = Tcl_GetHashValue(hPtr);
if (dPtr->proc != NULL) {
dPtr->proc(dPtr->clientData, interp);
}
ckfree(dPtr);
Tcl_DeleteHashEntry(hPtr);
}
/*
*----------------------------------------------------------------------
*
* Tcl_GetAssocData --
*
* Returns the client data associated with this name in the specified
* interpreter.
*
* Results:
* The client data in the AssocData record denoted by the named
* association, or NULL.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
ClientData
Tcl_GetAssocData(
Tcl_Interp *interp, /* Interpreter associated with. */
const char *name, /* Name of association. */
Tcl_InterpDeleteProc **procPtr)
/* Pointer to place to store address of
* current deletion callback. */
{
Interp *iPtr = (Interp *) interp;
AssocData *dPtr;
Tcl_HashEntry *hPtr;
if (iPtr->assocData == NULL) {
return NULL;
}
hPtr = Tcl_FindHashEntry(iPtr->assocData, name);
if (hPtr == NULL) {
return NULL;
}
dPtr = Tcl_GetHashValue(hPtr);
if (procPtr != NULL) {
*procPtr = dPtr->proc;
}
return dPtr->clientData;
}
/*
*----------------------------------------------------------------------
*
* Tcl_InterpDeleted --
*
* Returns nonzero if the interpreter has been deleted with a call to
* Tcl_DeleteInterp.
*
* Results:
* Nonzero if the interpreter is deleted, zero otherwise.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
int
Tcl_InterpDeleted(
Tcl_Interp *interp)
{
return (((Interp *) interp)->flags & DELETED) ? 1 : 0;
}
/*
*----------------------------------------------------------------------
*
* Tcl_DeleteInterp --
*
* Ensures that the interpreter will be deleted eventually. If there are
* no Tcl_Preserve calls in effect for this interpreter, it is deleted
* immediately, otherwise the interpreter is deleted when the last
* Tcl_Preserve is matched by a call to Tcl_Release. In either case, the
* function runs the currently registered deletion callbacks.
*
* Results:
* None.
*
* Side effects:
* The interpreter is marked as deleted. The caller may still use it
* safely if there are calls to Tcl_Preserve in effect for the
* interpreter, but further calls to Tcl_Eval etc in this interpreter
* will fail.
*
*----------------------------------------------------------------------
*/
void
Tcl_DeleteInterp(
Tcl_Interp *interp) /* Token for command interpreter (returned by
* a previous call to Tcl_CreateInterp). */
{
Interp *iPtr = (Interp *) interp;
/*
* If the interpreter has already been marked deleted, just punt.
*/
if (iPtr->flags & DELETED) {
return;
}
/*
* Mark the interpreter as deleted. No further evals will be allowed.
* Increase the compileEpoch as a signal to compiled bytecodes.
*/
iPtr->flags |= DELETED;
iPtr->compileEpoch++;
/*
* Ensure that the interpreter is eventually deleted.
*/
Tcl_EventuallyFree(interp, (Tcl_FreeProc *) DeleteInterpProc);
}
/*
*----------------------------------------------------------------------
*
* DeleteInterpProc --
*
* Helper function to delete an interpreter. This function is called when
* the last call to Tcl_Preserve on this interpreter is matched by a call
* to Tcl_Release. The function cleans up all resources used in the
* interpreter and calls all currently registered interpreter deletion
* callbacks.
*
* Results:
* None.
*
* Side effects:
* Whatever the interpreter deletion callbacks do. Frees resources used
* by the interpreter.
*
*----------------------------------------------------------------------
*/
static void
DeleteInterpProc(
Tcl_Interp *interp) /* Interpreter to delete. */
{
Interp *iPtr = (Interp *) interp;
Tcl_HashEntry *hPtr;
Tcl_HashSearch search;
Tcl_HashTable *hTablePtr;
ResolverScheme *resPtr, *nextResPtr;
int i;
/*
* Punt if there is an error in the Tcl_Release/Tcl_Preserve matchup,
* unless we are exiting.
*/
if ((iPtr->numLevels > 0) && !TclInExit()) {
Tcl_Panic("DeleteInterpProc called with active evals");
}
/*
* The interpreter should already be marked deleted; otherwise how did we
* get here?
*/
if (!(iPtr->flags & DELETED)) {
Tcl_Panic("DeleteInterpProc called on interpreter not marked deleted");
}
/*
* TIP #219, Tcl Channel Reflection API. Discard a leftover state.
*/
if (iPtr->chanMsg != NULL) {
Tcl_DecrRefCount(iPtr->chanMsg);
iPtr->chanMsg = NULL;
}
/*
* TIP #285, Script cancellation support. Delete this interp from the
* global hash table of CancelInfo structs.
*/
Tcl_MutexLock(&cancelLock);
hPtr = Tcl_FindHashEntry(&cancelTable, (char *) iPtr);
if (hPtr != NULL) {
CancelInfo *cancelInfo = Tcl_GetHashValue(hPtr);
if (cancelInfo != NULL) {
if (cancelInfo->result != NULL) {
ckfree(cancelInfo->result);
}
ckfree(cancelInfo);
}
Tcl_DeleteHashEntry(hPtr);
}
if (iPtr->asyncCancel != NULL) {
Tcl_AsyncDelete(iPtr->asyncCancel);
iPtr->asyncCancel = NULL;
}
if (iPtr->asyncCancelMsg != NULL) {
Tcl_DecrRefCount(iPtr->asyncCancelMsg);
iPtr->asyncCancelMsg = NULL;
}
Tcl_MutexUnlock(&cancelLock);
/*
* Shut down all limit handler callback scripts that call back into this
* interpreter. Then eliminate all limit handlers for this interpreter.
*/
TclRemoveScriptLimitCallbacks(interp);
TclLimitRemoveAllHandlers(interp);
/*
* Dismantle the namespace here, before we clear the assocData. If any
* background errors occur here, they will be deleted below.
*
* Dismantle the namespace after freeing the iPtr->handle so that each
* bytecode releases its literals without caring to update the literal
* table, as it will be freed later in this function without further use.
*/
TclHandleFree(iPtr->handle);
TclTeardownNamespace(iPtr->globalNsPtr);
/*
* Delete all the hidden commands.
*/
hTablePtr = iPtr->hiddenCmdTablePtr;
if (hTablePtr != NULL) {
/*
* Non-pernicious deletion. The deletion callbacks will not be allowed
* to create any new hidden or non-hidden commands.
* Tcl_DeleteCommandFromToken will remove the entry from the
* hiddenCmdTablePtr.
*/
hPtr = Tcl_FirstHashEntry(hTablePtr, &search);
for (; hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) {
Tcl_DeleteCommandFromToken(interp, Tcl_GetHashValue(hPtr));
}
Tcl_DeleteHashTable(hTablePtr);
ckfree(hTablePtr);
}
/*
* Invoke deletion callbacks; note that a callback can create new
* callbacks, so we iterate.
*/
while (iPtr->assocData != NULL) {
AssocData *dPtr;
hTablePtr = iPtr->assocData;
iPtr->assocData = NULL;
for (hPtr = Tcl_FirstHashEntry(hTablePtr, &search);
hPtr != NULL;
hPtr = Tcl_FirstHashEntry(hTablePtr, &search)) {
dPtr = Tcl_GetHashValue(hPtr);
Tcl_DeleteHashEntry(hPtr);
if (dPtr->proc != NULL) {
dPtr->proc(dPtr->clientData, interp);
}
ckfree(dPtr);
}
Tcl_DeleteHashTable(hTablePtr);
ckfree(hTablePtr);
}
/*
* Pop the root frame pointer and finish deleting the global
* namespace. The order is important [Bug 1658572].
*/
if ((iPtr->framePtr != iPtr->rootFramePtr) && !TclInExit()) {
Tcl_Panic("DeleteInterpProc: popping rootCallFrame with other frames on top");
}
Tcl_PopCallFrame(interp);
ckfree(iPtr->rootFramePtr);
iPtr->rootFramePtr = NULL;
Tcl_DeleteNamespace((Tcl_Namespace *) iPtr->globalNsPtr);
/*
* Free up the result *after* deleting variables, since variable deletion
* could have transferred ownership of the result string to Tcl.
*/
Tcl_FreeResult(interp);
iPtr->result = NULL;
Tcl_DecrRefCount(iPtr->objResultPtr);
iPtr->objResultPtr = NULL;
Tcl_DecrRefCount(iPtr->ecVar);
if (iPtr->errorCode) {
Tcl_DecrRefCount(iPtr->errorCode);
iPtr->errorCode = NULL;
}
Tcl_DecrRefCount(iPtr->eiVar);
if (iPtr->errorInfo) {
Tcl_DecrRefCount(iPtr->errorInfo);
iPtr->errorInfo = NULL;
}
Tcl_DecrRefCount(iPtr->errorStack);
iPtr->errorStack = NULL;
Tcl_DecrRefCount(iPtr->upLiteral);
Tcl_DecrRefCount(iPtr->callLiteral);
Tcl_DecrRefCount(iPtr->innerLiteral);
Tcl_DecrRefCount(iPtr->innerContext);
if (iPtr->returnOpts) {
Tcl_DecrRefCount(iPtr->returnOpts);
}
if (iPtr->appendResult != NULL) {
ckfree(iPtr->appendResult);
iPtr->appendResult = NULL;
}
TclFreePackageInfo(iPtr);
while (iPtr->tracePtr != NULL) {
Tcl_DeleteTrace((Tcl_Interp *) iPtr, (Tcl_Trace) iPtr->tracePtr);
}
if (iPtr->execEnvPtr != NULL) {
TclDeleteExecEnv(iPtr->execEnvPtr);
}
if (iPtr->scriptFile) {
Tcl_DecrRefCount(iPtr->scriptFile);
iPtr->scriptFile = NULL;
}
Tcl_DecrRefCount(iPtr->emptyObjPtr);
iPtr->emptyObjPtr = NULL;
resPtr = iPtr->resolverPtr;
while (resPtr) {
nextResPtr = resPtr->nextPtr;
ckfree(resPtr->name);
ckfree(resPtr);
resPtr = nextResPtr;
}
/*
* Free up literal objects created for scripts compiled by the
* interpreter.
*/
TclDeleteLiteralTable(interp, &iPtr->literalTable);
/*
* TIP #280 - Release the arrays for ByteCode/Proc extension, and
* contents.
*/
for (hPtr = Tcl_FirstHashEntry(iPtr->linePBodyPtr, &search);
hPtr != NULL;
hPtr = Tcl_NextHashEntry(&search)) {
CmdFrame *cfPtr = Tcl_GetHashValue(hPtr);
Proc *procPtr = (Proc *) Tcl_GetHashKey(iPtr->linePBodyPtr, hPtr);
procPtr->iPtr = NULL;
if (cfPtr) {
if (cfPtr->type == TCL_LOCATION_SOURCE) {
Tcl_DecrRefCount(cfPtr->data.eval.path);
}
ckfree(cfPtr->line);
ckfree(cfPtr);
}
Tcl_DeleteHashEntry(hPtr);
}
Tcl_DeleteHashTable(iPtr->linePBodyPtr);
ckfree(iPtr->linePBodyPtr);
iPtr->linePBodyPtr = NULL;
/*
* See also tclCompile.c, TclCleanupByteCode
*/
for (hPtr = Tcl_FirstHashEntry(iPtr->lineBCPtr, &search);
hPtr != NULL;
hPtr = Tcl_NextHashEntry(&search)) {
ExtCmdLoc *eclPtr = Tcl_GetHashValue(hPtr);
if (eclPtr->type == TCL_LOCATION_SOURCE) {
Tcl_DecrRefCount(eclPtr->path);
}
for (i=0; i< eclPtr->nuloc; i++) {
ckfree(eclPtr->loc[i].line);
}
if (eclPtr->loc != NULL) {
ckfree(eclPtr->loc);
}
ckfree(eclPtr);
Tcl_DeleteHashEntry(hPtr);
}
Tcl_DeleteHashTable(iPtr->lineBCPtr);
ckfree(iPtr->lineBCPtr);
iPtr->lineBCPtr = NULL;
/*
* Location stack for uplevel/eval/... scripts which were passed through
* proc arguments. Actually we track all arguments as we do not and cannot
* know which arguments will be used as scripts and which will not.
*/
if (iPtr->lineLAPtr->numEntries && !TclInExit()) {
/*
* When the interp goes away we have nothing on the stack, so there
* are no arguments, so this table has to be empty.
*/
Tcl_Panic("Argument location tracking table not empty");
}
Tcl_DeleteHashTable(iPtr->lineLAPtr);
ckfree((char *) iPtr->lineLAPtr);
iPtr->lineLAPtr = NULL;
if (iPtr->lineLABCPtr->numEntries && !TclInExit()) {
/*
* When the interp goes away we have nothing on the stack, so there
* are no arguments, so this table has to be empty.
*/
Tcl_Panic("Argument location tracking table not empty");
}
Tcl_DeleteHashTable(iPtr->lineLABCPtr);
ckfree(iPtr->lineLABCPtr);
iPtr->lineLABCPtr = NULL;
/*
* Squelch the tables of traces on variables and searches over arrays in
* the in the interpreter.
*/
Tcl_DeleteHashTable(&iPtr->varTraces);
Tcl_DeleteHashTable(&iPtr->varSearches);
ckfree(iPtr);
}
/*
*---------------------------------------------------------------------------
*
* Tcl_HideCommand --
*
* Makes a command hidden so that it cannot be invoked from within an
* interpreter, only from within an ancestor.
*
* Results:
* A standard Tcl result; also leaves a message in the interp's result if
* an error occurs.
*
* Side effects:
* Removes a command from the command table and create an entry into the
* hidden command table under the specified token name.
*
*---------------------------------------------------------------------------
*/
int
Tcl_HideCommand(
Tcl_Interp *interp, /* Interpreter in which to hide command. */
const char *cmdName, /* Name of command to hide. */
const char *hiddenCmdToken) /* Token name of the to-be-hidden command. */
{
Interp *iPtr = (Interp *) interp;
Tcl_Command cmd;
Command *cmdPtr;
Tcl_HashTable *hiddenCmdTablePtr;
Tcl_HashEntry *hPtr;
int isNew;
if (iPtr->flags & DELETED) {
/*
* The interpreter is being deleted. Do not create any new structures,
* because it is not safe to modify the interpreter.
*/
return TCL_ERROR;
}
/*
* Disallow hiding of commands that are currently in a namespace or
* renaming (as part of hiding) into a namespace (because the current
* implementation with a single global table and the needed uniqueness of
* names cause problems with namespaces).
*
* We don't need to check for "::" in cmdName because the real check is on
* the nsPtr below.
*
* hiddenCmdToken is just a string which is not interpreted in any way. It
* may contain :: but the string is not interpreted as a namespace
* qualifier command name. Thus, hiding foo::bar to foo::bar and then
* trying to expose or invoke ::foo::bar will NOT work; but if the
* application always uses the same strings it will get consistent
* behaviour.
*
* But as we currently limit ourselves to the global namespace only for
* the source, in order to avoid potential confusion, lets prevent "::" in
* the token too. - dl
*/
if (strstr(hiddenCmdToken, "::") != NULL) {
Tcl_SetObjResult(interp, Tcl_NewStringObj(
"cannot use namespace qualifiers in hidden command"
" token (rename)", -1));
Tcl_SetErrorCode(interp, "TCL", "VALUE", "HIDDENTOKEN", NULL);
return TCL_ERROR;
}
/*
* Find the command to hide. An error is returned if cmdName can't be
* found. Look up the command only from the global namespace. Full path of
* the command must be given if using namespaces.
*/
cmd = Tcl_FindCommand(interp, cmdName, NULL,
/*flags*/ TCL_LEAVE_ERR_MSG | TCL_GLOBAL_ONLY);
if (cmd == (Tcl_Command) NULL) {
return TCL_ERROR;
}
cmdPtr = (Command *) cmd;
/*
* Check that the command is really in global namespace
*/
if (cmdPtr->nsPtr != iPtr->globalNsPtr) {
Tcl_SetObjResult(interp, Tcl_NewStringObj(
"can only hide global namespace commands (use rename then hide)",
-1));
Tcl_SetErrorCode(interp, "TCL", "HIDE", "NON_GLOBAL", NULL);
return TCL_ERROR;
}
/*
* Initialize the hidden command table if necessary.
*/
hiddenCmdTablePtr = iPtr->hiddenCmdTablePtr;
if (hiddenCmdTablePtr == NULL) {
hiddenCmdTablePtr = ckalloc(sizeof(Tcl_HashTable));
Tcl_InitHashTable(hiddenCmdTablePtr, TCL_STRING_KEYS);
iPtr->hiddenCmdTablePtr = hiddenCmdTablePtr;
}
/*
* It is an error to move an exposed command to a hidden command with
* hiddenCmdToken if a hidden command with the name hiddenCmdToken already
* exists.
*/
hPtr = Tcl_CreateHashEntry(hiddenCmdTablePtr, hiddenCmdToken, &isNew);
if (!isNew) {
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
"hidden command named \"%s\" already exists",
hiddenCmdToken));
Tcl_SetErrorCode(interp, "TCL", "HIDE", "ALREADY_HIDDEN", NULL);
return TCL_ERROR;
}
/*
* NB: This code is currently 'like' a rename to a specialy set apart name
* table. Changes here and in TclRenameCommand must be kept in synch until
* the common parts are actually factorized out.
*/
/*
* Remove the hash entry for the command from the interpreter command
* table. This is like deleting the command, so bump its command epoch;
* this invalidates any cached references that point to the command.
*/
if (cmdPtr->hPtr != NULL) {
Tcl_DeleteHashEntry(cmdPtr->hPtr);
cmdPtr->hPtr = NULL;
cmdPtr->cmdEpoch++;
}
/*
* The list of command exported from the namespace might have changed.
* However, we do not need to recompute this just yet; next time we need
* the info will be soon enough.
*/
TclInvalidateNsCmdLookup(cmdPtr->nsPtr);
/*
* Now link the hash table entry with the command structure. We ensured
* above that the nsPtr was right.
*/
cmdPtr->hPtr = hPtr;
Tcl_SetHashValue(hPtr, cmdPtr);
/*
* If the command being hidden has a compile function, increment the
* interpreter's compileEpoch to invalidate its compiled code. This makes
* sure that we don't later try to execute old code compiled with
* command-specific (i.e., inline) bytecodes for the now-hidden command.
* This field is checked in Tcl_EvalObj and ObjInterpProc, and code whose
* compilation epoch doesn't match is recompiled.
*/
if (cmdPtr->compileProc != NULL) {
iPtr->compileEpoch++;
}
return TCL_OK;
}
/*
*----------------------------------------------------------------------
*
* Tcl_ExposeCommand --
*
* Makes a previously hidden command callable from inside the interpreter
* instead of only by its ancestors.
*
* Results:
* A standard Tcl result. If an error occurs, a message is left in the
* interp's result.
*
* Side effects:
* Moves commands from one hash table to another.
*
*----------------------------------------------------------------------
*/
int
Tcl_ExposeCommand(
Tcl_Interp *interp, /* Interpreter in which to make command
* callable. */
const char *hiddenCmdToken, /* Name of hidden command. */
const char *cmdName) /* Name of to-be-exposed command. */
{
Interp *iPtr = (Interp *) interp;
Command *cmdPtr;
Namespace *nsPtr;
Tcl_HashEntry *hPtr;
Tcl_HashTable *hiddenCmdTablePtr;
int isNew;
if (iPtr->flags & DELETED) {
/*
* The interpreter is being deleted. Do not create any new structures,
* because it is not safe to modify the interpreter.
*/
return TCL_ERROR;
}
/*
* Check that we have a regular name for the command (that the user is not
* trying to do an expose and a rename (to another namespace) at the same
* time).
*/
if (strstr(cmdName, "::") != NULL) {
Tcl_SetObjResult(interp, Tcl_NewStringObj(
"cannot expose to a namespace (use expose to toplevel, then rename)",
-1));
Tcl_SetErrorCode(interp, "TCL", "EXPOSE", "NON_GLOBAL", NULL);
return TCL_ERROR;
}
/*
* Get the command from the hidden command table:
*/
hPtr = NULL;
hiddenCmdTablePtr = iPtr->hiddenCmdTablePtr;
if (hiddenCmdTablePtr != NULL) {
hPtr = Tcl_FindHashEntry(hiddenCmdTablePtr, hiddenCmdToken);
}
if (hPtr == NULL) {
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
"unknown hidden command \"%s\"", hiddenCmdToken));
Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "HIDDENTOKEN",
hiddenCmdToken, NULL);
return TCL_ERROR;
}
cmdPtr = Tcl_GetHashValue(hPtr);
/*
* Check that we have a true global namespace command (enforced by
* Tcl_HideCommand but let's double check. (If it was not, we would not
* really know how to handle it).
*/
if (cmdPtr->nsPtr != iPtr->globalNsPtr) {
/*
* This case is theoritically impossible, we might rather Tcl_Panic
* than 'nicely' erroring out ?
*/
Tcl_SetObjResult(interp, Tcl_NewStringObj(
"trying to expose a non-global command namespace command",
-1));
return TCL_ERROR;
}
/*
* This is the global table.
*/
nsPtr = cmdPtr->nsPtr;
/*
* It is an error to overwrite an existing exposed command as a result of
* exposing a previously hidden command.
*/
hPtr = Tcl_CreateHashEntry(&nsPtr->cmdTable, cmdName, &isNew);
if (!isNew) {
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
"exposed command \"%s\" already exists", cmdName));
Tcl_SetErrorCode(interp, "TCL", "EXPOSE", "COMMAND_EXISTS", NULL);
return TCL_ERROR;
}
/*
* Command resolvers (per-interp, per-namespace) might have resolved to a
* command for the given namespace scope with this command not being
* registered with the namespace's command table. During BC compilation,
* the so-resolved command turns into a CmdName literal. Without
* invalidating a possible CmdName literal here explicitly, such literals
* keep being reused while pointing to overhauled commands.
*/
TclInvalidateCmdLiteral(interp, cmdName, nsPtr);
/*
* The list of command exported from the namespace might have changed.
* However, we do not need to recompute this just yet; next time we need
* the info will be soon enough.
*/
TclInvalidateNsCmdLookup(nsPtr);
/*
* Remove the hash entry for the command from the interpreter hidden
* command table.
*/
if (cmdPtr->hPtr != NULL) {
Tcl_DeleteHashEntry(cmdPtr->hPtr);
cmdPtr->hPtr = NULL;
}
/*
* Now link the hash table entry with the command structure. This is like
* creating a new command, so deal with any shadowing of commands in the
* global namespace.
*/
cmdPtr->hPtr = hPtr;
Tcl_SetHashValue(hPtr, cmdPtr);
/*
* Not needed as we are only in the global namespace (but would be needed
* again if we supported namespace command hiding)
*
* TclResetShadowedCmdRefs(interp, cmdPtr);
*/
/*
* If the command being exposed has a compile function, increment
* interpreter's compileEpoch to invalidate its compiled code. This makes
* sure that we don't later try to execute old code compiled assuming the
* command is hidden. This field is checked in Tcl_EvalObj and
* ObjInterpProc, and code whose compilation epoch doesn't match is
* recompiled.
*/
if (cmdPtr->compileProc != NULL) {
iPtr->compileEpoch++;
}
return TCL_OK;
}
/*
*----------------------------------------------------------------------
*
* Tcl_CreateCommand --
*
* Define a new command in a command table.
*
* Results:
* The return value is a token for the command, which can be used in
* future calls to Tcl_GetCommandName.
*
* Side effects:
* If a command named cmdName already exists for interp, it is deleted.
* In the future, when cmdName is seen as the name of a command by
* Tcl_Eval, proc will be called. To support the bytecode interpreter,
* the command is created with a wrapper Tcl_ObjCmdProc
* (TclInvokeStringCommand) that eventially calls proc. When the command
* is deleted from the table, deleteProc will be called. See the manual
* entry for details on the calling sequence.
*
*----------------------------------------------------------------------
*/
Tcl_Command
Tcl_CreateCommand(
Tcl_Interp *interp, /* Token for command interpreter returned by a
* previous call to Tcl_CreateInterp. */
const char *cmdName, /* Name of command. If it contains namespace
* qualifiers, the new command is put in the
* specified namespace; otherwise it is put in
* the global namespace. */
Tcl_CmdProc *proc, /* Function to associate with cmdName. */
ClientData clientData, /* Arbitrary value passed to string proc. */
Tcl_CmdDeleteProc *deleteProc)
/* If not NULL, gives a function to call when
* this command is deleted. */
{
Interp *iPtr = (Interp *) interp;
ImportRef *oldRefPtr = NULL;
Namespace *nsPtr;
Command *cmdPtr;
Tcl_HashEntry *hPtr;
const char *tail;
int isNew = 0, deleted = 0;
ImportedCmdData *dataPtr;
if (iPtr->flags & DELETED) {
/*
* The interpreter is being deleted. Don't create any new commands;
* it's not safe to muck with the interpreter anymore.
*/
return (Tcl_Command) NULL;
}
/*
* If the command name we seek to create already exists, we need to
* delete that first. That can be tricky in the presence of traces.
* Loop until we no longer find an existing command in the way, or
* until we've deleted one command and that didn't finish the job.
*/
while (1) {
/*
* Determine where the command should reside. If its name contains
* namespace qualifiers, we put it in the specified namespace;
* otherwise, we always put it in the global namespace.
*/
if (strstr(cmdName, "::") != NULL) {
Namespace *dummy1, *dummy2;
TclGetNamespaceForQualName(interp, cmdName, NULL,
TCL_CREATE_NS_IF_UNKNOWN, &nsPtr, &dummy1, &dummy2, &tail);
if ((nsPtr == NULL) || (tail == NULL)) {
return (Tcl_Command) NULL;
}
} else {
nsPtr = iPtr->globalNsPtr;
tail = cmdName;
}
hPtr = Tcl_CreateHashEntry(&nsPtr->cmdTable, tail, &isNew);
if (isNew || deleted) {
/*
* isNew - No conflict with existing command.
* deleted - We've already deleted a conflicting command
*/
break;
}
/*
* An existing command conflicts. Try to delete it...
*/
cmdPtr = Tcl_GetHashValue(hPtr);
/*
* Be careful to preserve any existing import links so we can restore
* them down below. That way, you can redefine a command and its
* import status will remain intact.
*/
cmdPtr->refCount++;
if (cmdPtr->importRefPtr) {
cmdPtr->flags |= CMD_REDEF_IN_PROGRESS;
}
Tcl_DeleteCommandFromToken(interp, (Tcl_Command) cmdPtr);
if (cmdPtr->flags & CMD_REDEF_IN_PROGRESS) {
oldRefPtr = cmdPtr->importRefPtr;
cmdPtr->importRefPtr = NULL;
}
TclCleanupCommandMacro(cmdPtr);
deleted = 1;
}
if (!isNew) {
/*
* If the deletion callback recreated the command, just throw away the
* new command (if we try to delete it again, we could get stuck in an
* infinite loop).
*/
ckfree(Tcl_GetHashValue(hPtr));
}
if (!deleted) {
/*
* Command resolvers (per-interp, per-namespace) might have resolved
* to a command for the given namespace scope with this command not
* being registered with the namespace's command table. During BC
* compilation, the so-resolved command turns into a CmdName literal.
* Without invalidating a possible CmdName literal here explicitly,
* such literals keep being reused while pointing to overhauled
* commands.
*/
TclInvalidateCmdLiteral(interp, tail, nsPtr);
/*
* The list of command exported from the namespace might have changed.
* However, we do not need to recompute this just yet; next time we
* need the info will be soon enough.
*/
TclInvalidateNsCmdLookup(nsPtr);
TclInvalidateNsPath(nsPtr);
}
cmdPtr = ckalloc(sizeof(Command));
Tcl_SetHashValue(hPtr, cmdPtr);
cmdPtr->hPtr = hPtr;
cmdPtr->nsPtr = nsPtr;
cmdPtr->refCount = 1;
cmdPtr->cmdEpoch = 0;
cmdPtr->compileProc = NULL;
cmdPtr->objProc = TclInvokeStringCommand;
cmdPtr->objClientData = cmdPtr;
cmdPtr->proc = proc;
cmdPtr->clientData = clientData;
cmdPtr->deleteProc = deleteProc;
cmdPtr->deleteData = clientData;
cmdPtr->flags = 0;
cmdPtr->importRefPtr = NULL;
cmdPtr->tracePtr = NULL;
cmdPtr->nreProc = NULL;
/*
* Plug in any existing import references found above. Be sure to update
* all of these references to point to the new command.
*/
if (oldRefPtr != NULL) {
cmdPtr->importRefPtr = oldRefPtr;
while (oldRefPtr != NULL) {
Command *refCmdPtr = oldRefPtr->importedCmdPtr;
dataPtr = refCmdPtr->objClientData;
dataPtr->realCmdPtr = cmdPtr;
oldRefPtr = oldRefPtr->nextPtr;
}
}
/*
* We just created a command, so in its namespace and all of its parent
* namespaces, it may shadow global commands with the same name. If any
* shadowed commands are found, invalidate all cached command references
* in the affected namespaces.
*/
TclResetShadowedCmdRefs(interp, cmdPtr);
return (Tcl_Command) cmdPtr;
}
/*
*----------------------------------------------------------------------
*
* Tcl_CreateObjCommand --
*
* Define a new object-based command in a command table.
*
* Results:
* The return value is a token for the command, which can be used in
* future calls to Tcl_GetCommandName.
*
* Side effects:
* If a command named "cmdName" already exists for interp, it is
* first deleted. Then the new command is created from the arguments.
* [***] (See below for exception).
*
* In the future, during bytecode evaluation when "cmdName" is seen as
* the name of a command by Tcl_EvalObj or Tcl_Eval, the object-based
* Tcl_ObjCmdProc proc will be called. When the command is deleted from
* the table, deleteProc will be called. See the manual entry for details
* on the calling sequence.
*
*----------------------------------------------------------------------
*/
Tcl_Command
Tcl_CreateObjCommand(
Tcl_Interp *interp, /* Token for command interpreter (returned by
* previous call to Tcl_CreateInterp). */
const char *cmdName, /* Name of command. If it contains namespace
* qualifiers, the new command is put in the
* specified namespace; otherwise it is put in
* the global namespace. */
Tcl_ObjCmdProc *proc, /* Object-based function to associate with
* name. */
ClientData clientData, /* Arbitrary value to pass to object
* function. */
Tcl_CmdDeleteProc *deleteProc
/* If not NULL, gives a function to call when
* this command is deleted. */
)
{
Interp *iPtr = (Interp *) interp;
Namespace *nsPtr;
const char *tail;
if (iPtr->flags & DELETED) {
/*
* The interpreter is being deleted. Don't create any new commands;
* it's not safe to muck with the interpreter anymore.
*/
return (Tcl_Command) NULL;
}
/*
* Determine where the command should reside. If its name contains
* namespace qualifiers, we put it in the specified namespace;
* otherwise, we always put it in the global namespace.
*/
if (strstr(cmdName, "::") != NULL) {
Namespace *dummy1, *dummy2;
TclGetNamespaceForQualName(interp, cmdName, NULL,
TCL_CREATE_NS_IF_UNKNOWN, &nsPtr, &dummy1, &dummy2, &tail);
if ((nsPtr == NULL) || (tail == NULL)) {
return (Tcl_Command) NULL;
}
} else {
nsPtr = iPtr->globalNsPtr;
tail = cmdName;
}
return TclCreateObjCommandInNs(interp, tail, (Tcl_Namespace *) nsPtr,
proc, clientData, deleteProc);
}
Tcl_Command
TclCreateObjCommandInNs (
Tcl_Interp *interp,
const char *cmdName, /* Name of command, without any namespace components */
Tcl_Namespace *namespace, /* The namespace to create the command in */
Tcl_ObjCmdProc *proc, /* Object-based function to associate with
* name. */
ClientData clientData, /* Arbitrary value to pass to object
* function. */
Tcl_CmdDeleteProc *deleteProc
/* If not NULL, gives a function to call when
* this command is deleted. */
) {
int deleted = 0, isNew = 0;
Command *cmdPtr;
ImportRef *oldRefPtr = NULL;
ImportedCmdData *dataPtr;
Tcl_HashEntry *hPtr;
Namespace *nsPtr = (Namespace *) namespace;
/*
* If the command name we seek to create already exists, we need to
* delete that first. That can be tricky in the presence of traces.
* Loop until we no longer find an existing command in the way, or
* until we've deleted one command and that didn't finish the job.
*/
while (1) {
hPtr = Tcl_CreateHashEntry(&nsPtr->cmdTable, cmdName, &isNew);
if (isNew || deleted) {
/*
* isNew - No conflict with existing command.
* deleted - We've already deleted a conflicting command
*/
break;
}
/*
* An existing command conflicts. Try to delete it...
*/
cmdPtr = Tcl_GetHashValue(hPtr);
/*
* [***] This is wrong. See Tcl Bug a16752c252.
* However, this buggy behavior is kept under particular circumstances
* to accommodate deployed binaries of the "tclcompiler" program
* <http://sourceforge.net/projects/tclpro/> that crash if the bug is
* fixed.
*/
if (cmdPtr->objProc == TclInvokeStringCommand
&& cmdPtr->clientData == clientData
&& cmdPtr->deleteData == clientData
&& cmdPtr->deleteProc == deleteProc) {
cmdPtr->objProc = proc;
cmdPtr->objClientData = clientData;
return (Tcl_Command) cmdPtr;
}
/*
* Otherwise, we delete the old command. Be careful to preserve any
* existing import links so we can restore them down below. That way,
* you can redefine a command and its import status will remain
* intact.
*/
cmdPtr->refCount++;
if (cmdPtr->importRefPtr) {
cmdPtr->flags |= CMD_REDEF_IN_PROGRESS;
}
/*
* Make sure namespace doesn't get deallocated.
*/
cmdPtr->nsPtr->refCount++;
Tcl_DeleteCommandFromToken(interp, (Tcl_Command) cmdPtr);
nsPtr = (Namespace *) TclEnsureNamespace(interp,
(Tcl_Namespace *)cmdPtr->nsPtr);
TclNsDecrRefCount(cmdPtr->nsPtr);
if (cmdPtr->flags & CMD_REDEF_IN_PROGRESS) {
oldRefPtr = cmdPtr->importRefPtr;
cmdPtr->importRefPtr = NULL;
}
TclCleanupCommandMacro(cmdPtr);
deleted = 1;
}
if (!isNew) {
/*
* If the deletion callback recreated the command, just throw away
* the new command (if we try to delete it again, we could get
* stuck in an infinite loop).
*/
ckfree(Tcl_GetHashValue(hPtr));
}
if (!deleted) {
/*
* Command resolvers (per-interp, per-namespace) might have resolved
* to a command for the given namespace scope with this command not
* being registered with the namespace's command table. During BC
* compilation, the so-resolved command turns into a CmdName literal.
* Without invalidating a possible CmdName literal here explicitly,
* such literals keep being reused while pointing to overhauled
* commands.
*/
TclInvalidateCmdLiteral(interp, cmdName, nsPtr);
/*
* The list of command exported from the namespace might have changed.
* However, we do not need to recompute this just yet; next time we
* need the info will be soon enough.
*/
TclInvalidateNsCmdLookup(nsPtr);
TclInvalidateNsPath(nsPtr);
}
cmdPtr = ckalloc(sizeof(Command));
Tcl_SetHashValue(hPtr, cmdPtr);
cmdPtr->hPtr = hPtr;
cmdPtr->nsPtr = nsPtr;
cmdPtr->refCount = 1;
cmdPtr->cmdEpoch = 0;
cmdPtr->compileProc = NULL;
cmdPtr->objProc = proc;
cmdPtr->objClientData = clientData;
cmdPtr->proc = TclInvokeObjectCommand;
cmdPtr->clientData = cmdPtr;
cmdPtr->deleteProc = deleteProc;
cmdPtr->deleteData = clientData;
cmdPtr->flags = 0;
cmdPtr->importRefPtr = NULL;
cmdPtr->tracePtr = NULL;
cmdPtr->nreProc = NULL;
/*
* Plug in any existing import references found above. Be sure to update
* all of these references to point to the new command.
*/
if (oldRefPtr != NULL) {
cmdPtr->importRefPtr = oldRefPtr;
while (oldRefPtr != NULL) {
Command *refCmdPtr = oldRefPtr->importedCmdPtr;
dataPtr = refCmdPtr->objClientData;
dataPtr->realCmdPtr = cmdPtr;
oldRefPtr = oldRefPtr->nextPtr;
}
}
/*
* We just created a command, so in its namespace and all of its parent
* namespaces, it may shadow global commands with the same name. If any
* shadowed commands are found, invalidate all cached command references
* in the affected namespaces.
*/
TclResetShadowedCmdRefs(interp, cmdPtr);
return (Tcl_Command) cmdPtr;
}
/*
*----------------------------------------------------------------------
*
* TclInvokeStringCommand --
*
* "Wrapper" Tcl_ObjCmdProc used to call an existing string-based
* Tcl_CmdProc if no object-based function exists for a command. A
* pointer to this function is stored as the Tcl_ObjCmdProc in a Command
* structure. It simply turns around and calls the string Tcl_CmdProc in
* the Command structure.
*
* Results:
* A standard Tcl object result value.
*
* Side effects:
* Besides those side effects of the called Tcl_CmdProc,
* TclInvokeStringCommand allocates and frees storage.
*
*----------------------------------------------------------------------
*/
int
TclInvokeStringCommand(
ClientData clientData, /* Points to command's Command structure. */
Tcl_Interp *interp, /* Current interpreter. */
int objc, /* Number of arguments. */
Tcl_Obj *const objv[]) /* Argument objects. */
{
Command *cmdPtr = clientData;
int i, result;
const char **argv =
TclStackAlloc(interp, (unsigned)(objc + 1) * sizeof(char *));
for (i = 0; i < objc; i++) {
argv[i] = Tcl_GetString(objv[i]);
}
argv[objc] = 0;
/*
* Invoke the command's string-based Tcl_CmdProc.
*/
result = cmdPtr->proc(cmdPtr->clientData, interp, objc, argv);
TclStackFree(interp, (void *) argv);
return result;
}
/*
*----------------------------------------------------------------------
*
* TclInvokeObjectCommand --
*
* "Wrapper" Tcl_CmdProc used to call an existing object-based
* Tcl_ObjCmdProc if no string-based function exists for a command. A
* pointer to this function is stored as the Tcl_CmdProc in a Command
* structure. It simply turns around and calls the object Tcl_ObjCmdProc
* in the Command structure.
*
* Results:
* A standard Tcl string result value.
*
* Side effects:
* Besides those side effects of the called Tcl_ObjCmdProc,
* TclInvokeObjectCommand allocates and frees storage.
*
*----------------------------------------------------------------------
*/
int
TclInvokeObjectCommand(
ClientData clientData, /* Points to command's Command structure. */
Tcl_Interp *interp, /* Current interpreter. */
int argc, /* Number of arguments. */
const char **argv) /* Argument strings. */
{
Command *cmdPtr = clientData;
Tcl_Obj *objPtr;
int i, length, result;
Tcl_Obj **objv =
TclStackAlloc(interp, (unsigned)(argc * sizeof(Tcl_Obj *)));
for (i = 0; i < argc; i++) {
length = strlen(argv[i]);
TclNewStringObj(objPtr, argv[i], length);
Tcl_IncrRefCount(objPtr);
objv[i] = objPtr;
}
/*
* Invoke the command's object-based Tcl_ObjCmdProc.
*/
if (cmdPtr->objProc != NULL) {
result = cmdPtr->objProc(cmdPtr->objClientData, interp, argc, objv);
} else {
result = Tcl_NRCallObjProc(interp, cmdPtr->nreProc,
cmdPtr->objClientData, argc, objv);
}
/*
* Move the interpreter's object result to the string result, then reset
* the object result.
*/
(void) Tcl_GetStringResult(interp);
/*
* Decrement the ref counts for the argument objects created above, then
* free the objv array if malloc'ed storage was used.
*/
for (i = 0; i < argc; i++) {
objPtr = objv[i];
Tcl_DecrRefCount(objPtr);
}
TclStackFree(interp, objv);
return result;
}
/*
*----------------------------------------------------------------------
*
* TclRenameCommand --
*
* Called to give an existing Tcl command a different name. Both the old
* command name and the new command name can have "::" namespace
* qualifiers. If the new command has a different namespace context, the
* command will be moved to that namespace and will execute in the
* context of that new namespace.
*
* If the new command name is NULL or the null string, the command is
* deleted.
*
* Results:
* Returns TCL_OK if successful, and TCL_ERROR if anything goes wrong.
*
* Side effects:
* If anything goes wrong, an error message is returned in the
* interpreter's result object.
*
*----------------------------------------------------------------------
*/
int
TclRenameCommand(
Tcl_Interp *interp, /* Current interpreter. */
const char *oldName, /* Existing command name. */
const char *newName) /* New command name. */
{
Interp *iPtr = (Interp *) interp;
const char *newTail;
Namespace *cmdNsPtr, *newNsPtr, *dummy1, *dummy2;
Tcl_Command cmd;
Command *cmdPtr;
Tcl_HashEntry *hPtr, *oldHPtr;
int isNew, result;
Tcl_Obj *oldFullName;
Tcl_DString newFullName;
/*
* Find the existing command. An error is returned if cmdName can't be
* found.
*/
cmd = Tcl_FindCommand(interp, oldName, NULL, /*flags*/ 0);
cmdPtr = (Command *) cmd;
if (cmdPtr == NULL) {
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
"can't %s \"%s\": command doesn't exist",
((newName == NULL)||(*newName == '\0'))? "delete":"rename",
oldName));
Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "COMMAND", oldName, NULL);
return TCL_ERROR;
}
/*
* If the new command name is NULL or empty, delete the command. Do this
* with Tcl_DeleteCommandFromToken, since we already have the command.
*/
if ((newName == NULL) || (*newName == '\0')) {
Tcl_DeleteCommandFromToken(interp, cmd);
return TCL_OK;
}
cmdNsPtr = cmdPtr->nsPtr;
oldFullName = Tcl_NewObj();
Tcl_IncrRefCount(oldFullName);
Tcl_GetCommandFullName(interp, cmd, oldFullName);
/*
* Make sure that the destination command does not already exist. The
* rename operation is like creating a command, so we should automatically
* create the containing namespaces just like Tcl_CreateCommand would.
*/
TclGetNamespaceForQualName(interp, newName, NULL,
TCL_CREATE_NS_IF_UNKNOWN, &newNsPtr, &dummy1, &dummy2, &newTail);
if ((newNsPtr == NULL) || (newTail == NULL)) {
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
"can't rename to \"%s\": bad command name", newName));
Tcl_SetErrorCode(interp, "TCL", "VALUE", "COMMAND", NULL);
result = TCL_ERROR;
goto done;
}
if (Tcl_FindHashEntry(&newNsPtr->cmdTable, newTail) != NULL) {
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
"can't rename to \"%s\": command already exists", newName));
Tcl_SetErrorCode(interp, "TCL", "OPERATION", "RENAME",
"TARGET_EXISTS", NULL);
result = TCL_ERROR;
goto done;
}
/*
* Warning: any changes done in the code here are likely to be needed in
* Tcl_HideCommand code too (until the common parts are extracted out).
* - dl
*/
/*
* Put the command in the new namespace so we can check for an alias loop.
* Since we are adding a new command to a namespace, we must handle any
* shadowing of the global commands that this might create.
*/
oldHPtr = cmdPtr->hPtr;
hPtr = Tcl_CreateHashEntry(&newNsPtr->cmdTable, newTail, &isNew);
Tcl_SetHashValue(hPtr, cmdPtr);
cmdPtr->hPtr = hPtr;
cmdPtr->nsPtr = newNsPtr;
TclResetShadowedCmdRefs(interp, cmdPtr);
/*
* Now check for an alias loop. If we detect one, put everything back the
* way it was and report the error.
*/
result = TclPreventAliasLoop(interp, interp, (Tcl_Command) cmdPtr);
if (result != TCL_OK) {
Tcl_DeleteHashEntry(cmdPtr->hPtr);
cmdPtr->hPtr = oldHPtr;
cmdPtr->nsPtr = cmdNsPtr;
goto done;
}
/*
* The list of command exported from the namespace might have changed.
* However, we do not need to recompute this just yet; next time we need
* the info will be soon enough. These might refer to the same variable,
* but that's no big deal.
*/
TclInvalidateNsCmdLookup(cmdNsPtr);
TclInvalidateNsCmdLookup(cmdPtr->nsPtr);
/*
* Command resolvers (per-interp, per-namespace) might have resolved to a
* command for the given namespace scope with this command not being
* registered with the namespace's command table. During BC compilation,
* the so-resolved command turns into a CmdName literal. Without
* invalidating a possible CmdName literal here explicitly, such literals
* keep being reused while pointing to overhauled commands.
*/
TclInvalidateCmdLiteral(interp, newTail, cmdPtr->nsPtr);
/*
* Script for rename traces can delete the command "oldName". Therefore
* increment the reference count for cmdPtr so that it's Command structure
* is freed only towards the end of this function by calling
* TclCleanupCommand.
*
* The trace function needs to get a fully qualified name for old and new
* commands [Tcl bug #651271], or else there's no way for the trace
* function to get the namespace from which the old command is being
* renamed!
*/
Tcl_DStringInit(&newFullName);
Tcl_DStringAppend(&newFullName, newNsPtr->fullName, -1);
if (newNsPtr != iPtr->globalNsPtr) {
TclDStringAppendLiteral(&newFullName, "::");
}
Tcl_DStringAppend(&newFullName, newTail, -1);
cmdPtr->refCount++;
CallCommandTraces(iPtr, cmdPtr, Tcl_GetString(oldFullName),
Tcl_DStringValue(&newFullName), TCL_TRACE_RENAME);
Tcl_DStringFree(&newFullName);
/*
* The new command name is okay, so remove the command from its current
* namespace. This is like deleting the command, so bump the cmdEpoch to
* invalidate any cached references to the command.
*/
Tcl_DeleteHashEntry(oldHPtr);
cmdPtr->cmdEpoch++;
/*
* If the command being renamed has a compile function, increment the
* interpreter's compileEpoch to invalidate its compiled code. This makes
* sure that we don't later try to execute old code compiled for the
* now-renamed command.
*/
if (cmdPtr->compileProc != NULL) {
iPtr->compileEpoch++;
}
/*
* Now free the Command structure, if the "oldName" command has been
* deleted by invocation of rename traces.
*/
TclCleanupCommandMacro(cmdPtr);
result = TCL_OK;
done:
TclDecrRefCount(oldFullName);
return result;
}
/*
*----------------------------------------------------------------------
*
* Tcl_SetCommandInfo --
*
* Modifies various information about a Tcl command. Note that this
* function will not change a command's namespace; use TclRenameCommand
* to do that. Also, the isNativeObjectProc member of *infoPtr is
* ignored.
*
* Results:
* If cmdName exists in interp, then the information at *infoPtr is
* stored with the command in place of the current information and 1 is
* returned. If the command doesn't exist then 0 is returned.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
int
Tcl_SetCommandInfo(
Tcl_Interp *interp, /* Interpreter in which to look for
* command. */
const char *cmdName, /* Name of desired command. */
const Tcl_CmdInfo *infoPtr) /* Where to find information to store in the
* command. */
{
Tcl_Command cmd;
cmd = Tcl_FindCommand(interp, cmdName, NULL, /*flags*/ 0);
return Tcl_SetCommandInfoFromToken(cmd, infoPtr);
}
/*
*----------------------------------------------------------------------
*
* Tcl_SetCommandInfoFromToken --
*
* Modifies various information about a Tcl command. Note that this
* function will not change a command's namespace; use TclRenameCommand
* to do that. Also, the isNativeObjectProc member of *infoPtr is
* ignored.
*
* Results:
* If cmdName exists in interp, then the information at *infoPtr is
* stored with the command in place of the current information and 1 is
* returned. If the command doesn't exist then 0 is returned.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
int
Tcl_SetCommandInfoFromToken(
Tcl_Command cmd,
const Tcl_CmdInfo *infoPtr)
{
Command *cmdPtr; /* Internal representation of the command */
if (cmd == NULL) {
return 0;
}
/*
* The isNativeObjectProc and nsPtr members of *infoPtr are ignored.
*/
cmdPtr = (Command *) cmd;
cmdPtr->proc = infoPtr->proc;
cmdPtr->clientData = infoPtr->clientData;
if (infoPtr->objProc == NULL) {
cmdPtr->objProc = TclInvokeStringCommand;
cmdPtr->objClientData = cmdPtr;
cmdPtr->nreProc = NULL;
} else {
if (infoPtr->objProc != cmdPtr->objProc) {
cmdPtr->nreProc = NULL;
cmdPtr->objProc = infoPtr->objProc;
}
cmdPtr->objClientData = infoPtr->objClientData;
}
cmdPtr->deleteProc = infoPtr->deleteProc;
cmdPtr->deleteData = infoPtr->deleteData;
return 1;
}
/*
*----------------------------------------------------------------------
*
* Tcl_GetCommandInfo --
*
* Returns various information about a Tcl command.
*
* Results:
* If cmdName exists in interp, then *infoPtr is modified to hold
* information about cmdName and 1 is returned. If the command doesn't
* exist then 0 is returned and *infoPtr isn't modified.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
int
Tcl_GetCommandInfo(
Tcl_Interp *interp, /* Interpreter in which to look for
* command. */
const char *cmdName, /* Name of desired command. */
Tcl_CmdInfo *infoPtr) /* Where to store information about
* command. */
{
Tcl_Command cmd;
cmd = Tcl_FindCommand(interp, cmdName, NULL, /*flags*/ 0);
return Tcl_GetCommandInfoFromToken(cmd, infoPtr);
}
/*
*----------------------------------------------------------------------
*
* Tcl_GetCommandInfoFromToken --
*
* Returns various information about a Tcl command.
*
* Results:
* Copies information from the command identified by 'cmd' into a
* caller-supplied structure and returns 1. If the 'cmd' is NULL, leaves
* the structure untouched and returns 0.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
int
Tcl_GetCommandInfoFromToken(
Tcl_Command cmd,
Tcl_CmdInfo *infoPtr)
{
Command *cmdPtr; /* Internal representation of the command */
if (cmd == NULL) {
return 0;
}
/*
* Set isNativeObjectProc 1 if objProc was registered by a call to
* Tcl_CreateObjCommand. Otherwise set it to 0.
*/
cmdPtr = (Command *) cmd;
infoPtr->isNativeObjectProc =
(cmdPtr->objProc != TclInvokeStringCommand);
infoPtr->objProc = cmdPtr->objProc;
infoPtr->objClientData = cmdPtr->objClientData;
infoPtr->proc = cmdPtr->proc;
infoPtr->clientData = cmdPtr->clientData;
infoPtr->deleteProc = cmdPtr->deleteProc;
infoPtr->deleteData = cmdPtr->deleteData;
infoPtr->namespacePtr = (Tcl_Namespace *) cmdPtr->nsPtr;
return 1;
}
/*
*----------------------------------------------------------------------
*
* Tcl_GetCommandName --
*
* Given a token returned by Tcl_CreateCommand, this function returns the
* current name of the command (which may have changed due to renaming).
*
* Results:
* The return value is the name of the given command.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
const char *
Tcl_GetCommandName(
Tcl_Interp *interp, /* Interpreter containing the command. */
Tcl_Command command) /* Token for command returned by a previous
* call to Tcl_CreateCommand. The command must
* not have been deleted. */
{
Command *cmdPtr = (Command *) command;
if ((cmdPtr == NULL) || (cmdPtr->hPtr == NULL)) {
/*
* This should only happen if command was "created" after the
* interpreter began to be deleted, so there isn't really any command.
* Just return an empty string.
*/
return "";
}
return Tcl_GetHashKey(cmdPtr->hPtr->tablePtr, cmdPtr->hPtr);
}
/*
*----------------------------------------------------------------------
*
* Tcl_GetCommandFullName --
*
* Given a token returned by, e.g., Tcl_CreateCommand or Tcl_FindCommand,
* this function appends to an object the command's full name, qualified
* by a sequence of parent namespace names. The command's fully-qualified
* name may have changed due to renaming.
*
* Results:
* None.
*
* Side effects:
* The command's fully-qualified name is appended to the string
* representation of objPtr.
*
*----------------------------------------------------------------------
*/
void
Tcl_GetCommandFullName(
Tcl_Interp *interp, /* Interpreter containing the command. */
Tcl_Command command, /* Token for command returned by a previous
* call to Tcl_CreateCommand. The command must
* not have been deleted. */
Tcl_Obj *objPtr) /* Points to the object onto which the
* command's full name is appended. */
{
Interp *iPtr = (Interp *) interp;
Command *cmdPtr = (Command *) command;
char *name;
/*
* Add the full name of the containing namespace, followed by the "::"
* separator, and the command name.
*/
if (cmdPtr != NULL) {
if (cmdPtr->nsPtr != NULL) {
Tcl_AppendToObj(objPtr, cmdPtr->nsPtr->fullName, -1);
if (cmdPtr->nsPtr != iPtr->globalNsPtr) {
Tcl_AppendToObj(objPtr, "::", 2);
}
}
if (cmdPtr->hPtr != NULL) {
name = Tcl_GetHashKey(cmdPtr->hPtr->tablePtr, cmdPtr->hPtr);
Tcl_AppendToObj(objPtr, name, -1);
}
}
}
/*
*----------------------------------------------------------------------
*
* Tcl_DeleteCommand --
*
* Remove the given command from the given interpreter.
*
* Results:
* 0 is returned if the command was deleted successfully. -1 is returned
* if there didn't exist a command by that name.
*
* Side effects:
* cmdName will no longer be recognized as a valid command for interp.
*
*----------------------------------------------------------------------
*/
int
Tcl_DeleteCommand(
Tcl_Interp *interp, /* Token for command interpreter (returned by
* a previous Tcl_CreateInterp call). */
const char *cmdName) /* Name of command to remove. */
{
Tcl_Command cmd;
/*
* Find the desired command and delete it.
*/
cmd = Tcl_FindCommand(interp, cmdName, NULL, /*flags*/ 0);
if (cmd == NULL) {
return -1;
}
return Tcl_DeleteCommandFromToken(interp, cmd);
}
/*
*----------------------------------------------------------------------
*
* Tcl_DeleteCommandFromToken --
*
* Removes the given command from the given interpreter. This function
* resembles Tcl_DeleteCommand, but takes a Tcl_Command token instead of
* a command name for efficiency.
*
* Results:
* 0 is returned if the command was deleted successfully. -1 is returned
* if there didn't exist a command by that name.
*
* Side effects:
* The command specified by "cmd" will no longer be recognized as a valid
* command for "interp".
*
*----------------------------------------------------------------------
*/
int
Tcl_DeleteCommandFromToken(
Tcl_Interp *interp, /* Token for command interpreter returned by a
* previous call to Tcl_CreateInterp. */
Tcl_Command cmd) /* Token for command to delete. */
{
Interp *iPtr = (Interp *) interp;
Command *cmdPtr = (Command *) cmd;
ImportRef *refPtr, *nextRefPtr;
Tcl_Command importCmd;
/*
* Bump the command epoch counter. This will invalidate all cached
* references that point to this command.
*/
cmdPtr->cmdEpoch++;
/*
* The code here is tricky. We can't delete the hash table entry before
* invoking the deletion callback because there are cases where the
* deletion callback needs to invoke the command (e.g. object systems such
* as OTcl). However, this means that the callback could try to delete or
* rename the command. The deleted flag allows us to detect these cases
* and skip nested deletes.
*/
if (cmdPtr->flags & CMD_IS_DELETED) {
/*
* Another deletion is already in progress. Remove the hash table
* entry now, but don't invoke a callback or free the command
* structure. Take care to only remove the hash entry if it has not
* already been removed; otherwise if we manage to hit this function
* three times, everything goes up in smoke. [Bug 1220058]
*/
if (cmdPtr->hPtr != NULL) {
Tcl_DeleteHashEntry(cmdPtr->hPtr);
cmdPtr->hPtr = NULL;
}
return 0;
}
/*
* We must delete this command, even though both traces and delete procs
* may try to avoid this (renaming the command etc). Also traces and
* delete procs may try to delete the command themselves. This flag
* declares that a delete is in progress and that recursive deletes should
* be ignored.
*/
cmdPtr->flags |= CMD_IS_DELETED;
/*
* Call trace functions for the command being deleted. Then delete its
* traces.
*/
cmdPtr->nsPtr->refCount++;
if (cmdPtr->tracePtr != NULL) {
CommandTrace *tracePtr;
CallCommandTraces(iPtr,cmdPtr,NULL,NULL,TCL_TRACE_DELETE);
/*
* Now delete these traces.
*/
tracePtr = cmdPtr->tracePtr;
while (tracePtr != NULL) {
CommandTrace *nextPtr = tracePtr->nextPtr;
if (tracePtr->refCount-- <= 1) {
ckfree(tracePtr);
}
tracePtr = nextPtr;
}
cmdPtr->tracePtr = NULL;
}
/*
* The list of command exported from the namespace might have changed.
* However, we do not need to recompute this just yet; next time we need
* the info will be soon enough.
*/
TclInvalidateNsCmdLookup(cmdPtr->nsPtr);
TclNsDecrRefCount(cmdPtr->nsPtr);
/*
* If the command being deleted has a compile function, increment the
* interpreter's compileEpoch to invalidate its compiled code. This makes
* sure that we don't later try to execute old code compiled with
* command-specific (i.e., inline) bytecodes for the now-deleted command.
* This field is checked in Tcl_EvalObj and ObjInterpProc, and code whose
* compilation epoch doesn't match is recompiled.
*/
if (cmdPtr->compileProc != NULL) {
iPtr->compileEpoch++;
}
/*
* Delete any imports of this routine elsewhere before calling deleteProc
* to that traces on the imports don't reference deallocated storage.
*/
if (!(cmdPtr->flags & CMD_REDEF_IN_PROGRESS)) {
for (refPtr = cmdPtr->importRefPtr; refPtr != NULL;
refPtr = nextRefPtr) {
nextRefPtr = refPtr->nextPtr;
importCmd = (Tcl_Command) refPtr->importedCmdPtr;
Tcl_DeleteCommandFromToken(interp, importCmd);
}
}
if (cmdPtr->deleteProc != NULL) {
/*
* Delete the command's client data. If this was an imported command
* created when a command was imported into a namespace, this client
* data will be a pointer to a ImportedCmdData structure describing
* the "real" command that this imported command refers to.
*
* If you are getting a crash during the call to deleteProc and
* cmdPtr->deleteProc is a pointer to the function free(), the most
* likely cause is that your extension allocated memory for the
* clientData argument to Tcl_CreateObjCommand with the ckalloc()
* macro and you are now trying to deallocate this memory with free()
* instead of ckfree(). You should pass a pointer to your own method
* that calls ckfree().
*/
cmdPtr->deleteProc(cmdPtr->deleteData);
}
/*
* Don't use hPtr to delete the hash entry here, because it's possible
* that the deletion callback renamed the command. Instead, use
* cmdPtr->hptr, and make sure that no-one else has already deleted the
* hash entry.
*/
if (cmdPtr->hPtr != NULL) {
Tcl_DeleteHashEntry(cmdPtr->hPtr);
cmdPtr->hPtr = NULL;
}
/*
* A number of tests for particular kinds of commands are done by checking
* whether the objProc field holds a known value. Set the field to NULL so
* that such tests won't have false positives when applied to deleted
* commands.
*/
cmdPtr->objProc = NULL;
/*
* Now free the Command structure, unless there is another reference to it
* from a CmdName Tcl object in some ByteCode code sequence. In that case,
* delay the cleanup until all references are either discarded (when a
* ByteCode is freed) or replaced by a new reference (when a cached
* CmdName Command reference is found to be invalid and
* TclNRExecuteByteCode looks up the command in the command hashtable).
*/
cmdPtr->flags |= CMD_DEAD;
TclCleanupCommandMacro(cmdPtr);
return 0;
}
/*
*----------------------------------------------------------------------
*
* CallCommandTraces --
*
* Abstraction of the code to call traces on a command.
*
* Results:
* Currently always NULL.
*
* Side effects:
* Anything; this may recursively evaluate scripts and code exists to do
* just that.
*
*----------------------------------------------------------------------
*/
static char *
CallCommandTraces(
Interp *iPtr, /* Interpreter containing command. */
Command *cmdPtr, /* Command whose traces are to be invoked. */
const char *oldName, /* Command's old name, or NULL if we must get
* the name from cmdPtr */
const char *newName, /* Command's new name, or NULL if the command
* is not being renamed */
int flags) /* Flags indicating the type of traces to
* trigger, either TCL_TRACE_DELETE or
* TCL_TRACE_RENAME. */
{
CommandTrace *tracePtr;
ActiveCommandTrace active;
char *result;
Tcl_Obj *oldNamePtr = NULL;
Tcl_InterpState state = NULL;
if (cmdPtr->flags & CMD_TRACE_ACTIVE) {
/*
* While a rename trace is active, we will not process any more rename
* traces; while a delete trace is active we will never reach here -
* because Tcl_DeleteCommandFromToken checks for the condition
* (cmdPtr->flags & CMD_IS_DELETED) and returns immediately when a
* command deletion is in progress. For all other traces, delete
* traces will not be invoked but a call to TraceCommandProc will
* ensure that tracePtr->clientData is freed whenever the command
* "oldName" is deleted.
*/
if (cmdPtr->flags & TCL_TRACE_RENAME) {
flags &= ~TCL_TRACE_RENAME;
}
if (flags == 0) {
return NULL;
}
}
cmdPtr->flags |= CMD_TRACE_ACTIVE;
result = NULL;
active.nextPtr = iPtr->activeCmdTracePtr;
active.reverseScan = 0;
iPtr->activeCmdTracePtr = &active;
if (flags & TCL_TRACE_DELETE) {
flags |= TCL_TRACE_DESTROYED;
}
active.cmdPtr = cmdPtr;
Tcl_Preserve(iPtr);
for (tracePtr = cmdPtr->tracePtr; tracePtr != NULL;
tracePtr = active.nextTracePtr) {
active.nextTracePtr = tracePtr->nextPtr;
if (!(tracePtr->flags & flags)) {
continue;
}
cmdPtr->flags |= tracePtr->flags;
if (oldName == NULL) {
TclNewObj(oldNamePtr);
Tcl_IncrRefCount(oldNamePtr);
Tcl_GetCommandFullName((Tcl_Interp *) iPtr,
(Tcl_Command) cmdPtr, oldNamePtr);
oldName = TclGetString(oldNamePtr);
}
tracePtr->refCount++;
if (state == NULL) {
state = Tcl_SaveInterpState((Tcl_Interp *) iPtr, TCL_OK);
}
tracePtr->traceProc(tracePtr->clientData, (Tcl_Interp *) iPtr,
oldName, newName, flags);
cmdPtr->flags &= ~tracePtr->flags;
if (tracePtr->refCount-- <= 1) {
ckfree(tracePtr);
}
}
if (state) {
Tcl_RestoreInterpState((Tcl_Interp *) iPtr, state);
}
/*
* If a new object was created to hold the full oldName, free it now.
*/
if (oldNamePtr != NULL) {
TclDecrRefCount(oldNamePtr);
}
/*
* Restore the variable's flags, remove the record of our active traces,
* and then return.
*/
cmdPtr->flags &= ~CMD_TRACE_ACTIVE;
iPtr->activeCmdTracePtr = active.nextPtr;
Tcl_Release(iPtr);
return result;
}
/*
*----------------------------------------------------------------------
*
* CancelEvalProc --
*
* Marks this interpreter as being canceled. This causes current
* executions to be unwound as the interpreter enters a state where it
* refuses to execute more commands or handle [catch] or [try], yet the
* interpreter is still able to execute further commands after the
* cancelation is cleared (unlike if it is deleted).
*
* Results:
* The value given for the code argument.
*
* Side effects:
* Transfers a message from the cancelation message to the interpreter.
*
*----------------------------------------------------------------------
*/
static int
CancelEvalProc(
ClientData clientData, /* Interp to cancel the script in progress. */
Tcl_Interp *interp, /* Ignored */
int code) /* Current return code from command. */
{
CancelInfo *cancelInfo = clientData;
Interp *iPtr;
if (cancelInfo != NULL) {
Tcl_MutexLock(&cancelLock);
iPtr = (Interp *) cancelInfo->interp;
if (iPtr != NULL) {
/*
* Setting the CANCELED flag will cause the script in progress to
* be canceled as soon as possible. The core honors this flag at
* all the necessary places to ensure script cancellation is
* responsive. Extensions can check for this flag by calling
* Tcl_Canceled and checking if TCL_ERROR is returned or they can
* choose to ignore the script cancellation flag and the
* associated functionality altogether. Currently, the only other
* flag we care about here is the TCL_CANCEL_UNWIND flag (from
* Tcl_CancelEval). We do not want to simply combine all the flags
* from original Tcl_CancelEval call with the interp flags here
* just in case the caller passed flags that might cause behaviour
* unrelated to script cancellation.
*/
TclSetCancelFlags(iPtr, cancelInfo->flags | CANCELED);
/*
* Now, we must set the script cancellation flags on all the child
* interpreters belonging to this one.
*/
TclSetChildCancelFlags((Tcl_Interp *) iPtr,
cancelInfo->flags | CANCELED, 0);
/*
* Create the result object now so that Tcl_Canceled can avoid
* locking the cancelLock mutex.
*/
if (cancelInfo->result != NULL) {
Tcl_SetStringObj(iPtr->asyncCancelMsg, cancelInfo->result,
cancelInfo->length);
} else {
Tcl_SetObjLength(iPtr->asyncCancelMsg, 0);
}
}
Tcl_MutexUnlock(&cancelLock);
}
return code;
}
/*
*----------------------------------------------------------------------
*
* TclCleanupCommand --
*
* This function frees up a Command structure unless it is still
* referenced from an interpreter's command hashtable or from a CmdName
* Tcl object representing the name of a command in a ByteCode
* instruction sequence.
*
* Results:
* None.
*
* Side effects:
* Memory gets freed unless a reference to the Command structure still
* exists. In that case the cleanup is delayed until the command is
* deleted or when the last ByteCode referring to it is freed.
*
*----------------------------------------------------------------------
*/
void
TclCleanupCommand(
Command *cmdPtr) /* Points to the Command structure to
* be freed. */
{
cmdPtr->refCount--;
if (cmdPtr->refCount <= 0) {
ckfree(cmdPtr);
}
}
/*
*----------------------------------------------------------------------
*
* Tcl_CreateMathFunc --
*
* Creates a new math function for expressions in a given interpreter.
*
* Results:
* None.
*
* Side effects:
* The Tcl function defined by "name" is created or redefined. If the
* function already exists then its definition is replaced; this includes
* the builtin functions. Redefining a builtin function forces all
* existing code to be invalidated since that code may be compiled using
* an instruction specific to the replaced function. In addition,
* redefioning a non-builtin function will force existing code to be
* invalidated if the number of arguments has changed.
*
*----------------------------------------------------------------------
*/
void
Tcl_CreateMathFunc(
Tcl_Interp *interp, /* Interpreter in which function is to be
* available. */
const char *name, /* Name of function (e.g. "sin"). */
int numArgs, /* Nnumber of arguments required by
* function. */
Tcl_ValueType *argTypes, /* Array of types acceptable for each
* argument. */
Tcl_MathProc *proc, /* C function that implements the math
* function. */
ClientData clientData) /* Additional value to pass to the
* function. */
{
Tcl_DString bigName;
OldMathFuncData *data = ckalloc(sizeof(OldMathFuncData));
data->proc = proc;
data->numArgs = numArgs;
data->argTypes = ckalloc(numArgs * sizeof(Tcl_ValueType));
memcpy(data->argTypes, argTypes, numArgs * sizeof(Tcl_ValueType));
data->clientData = clientData;
Tcl_DStringInit(&bigName);
TclDStringAppendLiteral(&bigName, "::tcl::mathfunc::");
Tcl_DStringAppend(&bigName, name, -1);
Tcl_CreateObjCommand(interp, Tcl_DStringValue(&bigName),
OldMathFuncProc, data, OldMathFuncDeleteProc);
Tcl_DStringFree(&bigName);
}
/*
*----------------------------------------------------------------------
*
* OldMathFuncProc --
*
* Dispatch to a math function created with Tcl_CreateMathFunc
*
* Results:
* Returns a standard Tcl result.
*
* Side effects:
* Whatever the math function does.
*
*----------------------------------------------------------------------
*/
static int
OldMathFuncProc(
ClientData clientData, /* Ponter to OldMathFuncData describing the
* function being called */
Tcl_Interp *interp, /* Tcl interpreter */
int objc, /* Actual parameter count */
Tcl_Obj *const *objv) /* Parameter vector */
{
Tcl_Obj *valuePtr;
OldMathFuncData *dataPtr = clientData;
Tcl_Value funcResult, *args;
int result;
int j, k;
double d;
/*
* Check argument count.
*/
if (objc != dataPtr->numArgs + 1) {
MathFuncWrongNumArgs(interp, dataPtr->numArgs+1, objc, objv);
return TCL_ERROR;
}
/*
* Convert arguments from Tcl_Obj's to Tcl_Value's.
*/
args = ckalloc(dataPtr->numArgs * sizeof(Tcl_Value));
for (j = 1, k = 0; j < objc; ++j, ++k) {
/* TODO: Convert to TclGetNumberFromObj? */
valuePtr = objv[j];
result = Tcl_GetDoubleFromObj(NULL, valuePtr, &d);
#ifdef ACCEPT_NAN
if ((result != TCL_OK) && (valuePtr->typePtr == &tclDoubleType)) {
d = valuePtr->internalRep.doubleValue;
result = TCL_OK;
}
#endif
if (result != TCL_OK) {
/*
* We have a non-numeric argument.
*/
Tcl_SetObjResult(interp, Tcl_NewStringObj(
"argument to math function didn't have numeric value",
-1));
TclCheckBadOctal(interp, Tcl_GetString(valuePtr));
ckfree(args);
return TCL_ERROR;
}
/*
* Copy the object's numeric value to the argument record, converting
* it if necessary.
*
* NOTE: no bignum support; use the new mathfunc interface for that.
*/
args[k].type = dataPtr->argTypes[k];
switch (args[k].type) {
case TCL_EITHER:
if (Tcl_GetLongFromObj(NULL, valuePtr, &args[k].intValue)
== TCL_OK) {
args[k].type = TCL_INT;
break;
}
if (TclGetWideIntFromObj(interp, valuePtr, &args[k].wideValue)
== TCL_OK) {
args[k].type = TCL_WIDE_INT;
break;
}
args[k].type = TCL_DOUBLE;
/* FALLTHROUGH */
case TCL_DOUBLE:
args[k].doubleValue = d;
break;
case TCL_INT:
if (ExprIntFunc(NULL, interp, 2, &objv[j-1]) != TCL_OK) {
ckfree(args);
return TCL_ERROR;
}
valuePtr = Tcl_GetObjResult(interp);
Tcl_GetLongFromObj(NULL, valuePtr, &args[k].intValue);
Tcl_ResetResult(interp);
break;
case TCL_WIDE_INT:
if (ExprWideFunc(NULL, interp, 2, &objv[j-1]) != TCL_OK) {
ckfree(args);
return TCL_ERROR;
}
valuePtr = Tcl_GetObjResult(interp);
TclGetWideIntFromObj(NULL, valuePtr, &args[k].wideValue);
Tcl_ResetResult(interp);
break;
}
}
/*
* Call the function.
*/
errno = 0;
result = dataPtr->proc(dataPtr->clientData, interp, args, &funcResult);
ckfree(args);
if (result != TCL_OK) {
return result;
}
/*
* Return the result of the call.
*/
if (funcResult.type == TCL_INT) {
TclNewLongObj(valuePtr, funcResult.intValue);
} else if (funcResult.type == TCL_WIDE_INT) {
valuePtr = Tcl_NewWideIntObj(funcResult.wideValue);
} else {
return CheckDoubleResult(interp, funcResult.doubleValue);
}
Tcl_SetObjResult(interp, valuePtr);
return TCL_OK;
}
/*
*----------------------------------------------------------------------
*
* OldMathFuncDeleteProc --
*
* Cleans up after deleting a math function registered with
* Tcl_CreateMathFunc
*
* Results:
* None.
*
* Side effects:
* Frees allocated memory.
*
*----------------------------------------------------------------------
*/
static void
OldMathFuncDeleteProc(
ClientData clientData)
{
OldMathFuncData *dataPtr = clientData;
ckfree(dataPtr->argTypes);
ckfree(dataPtr);
}
/*
*----------------------------------------------------------------------
*
* Tcl_GetMathFuncInfo --
*
* Discovers how a particular math function was created in a given
* interpreter.
*
* Results:
* TCL_OK if it succeeds, TCL_ERROR else (leaving an error message in the
* interpreter result if that happens.)
*
* Side effects:
* If this function succeeds, the variables pointed to by the numArgsPtr
* and argTypePtr arguments will be updated to detail the arguments
* allowed by the function. The variable pointed to by the procPtr
* argument will be set to NULL if the function is a builtin function,
* and will be set to the address of the C function used to implement the
* math function otherwise (in which case the variable pointed to by the
* clientDataPtr argument will also be updated.)
*
*----------------------------------------------------------------------
*/
int
Tcl_GetMathFuncInfo(
Tcl_Interp *interp,
const char *name,
int *numArgsPtr,
Tcl_ValueType **argTypesPtr,
Tcl_MathProc **procPtr,
ClientData *clientDataPtr)
{
Tcl_Obj *cmdNameObj;
Command *cmdPtr;
/*
* Get the command that implements the math function.
*/
TclNewLiteralStringObj(cmdNameObj, "tcl::mathfunc::");
Tcl_AppendToObj(cmdNameObj, name, -1);
Tcl_IncrRefCount(cmdNameObj);
cmdPtr = (Command *) Tcl_GetCommandFromObj(interp, cmdNameObj);
Tcl_DecrRefCount(cmdNameObj);
/*
* Report unknown functions.
*/
if (cmdPtr == NULL) {
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
"unknown math function \"%s\"", name));
Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "MATHFUNC", name, NULL);
*numArgsPtr = -1;
*argTypesPtr = NULL;
*procPtr = NULL;
*clientDataPtr = NULL;
return TCL_ERROR;
}
/*
* Retrieve function info for user defined functions; return dummy
* information for builtins.
*/
if (cmdPtr->objProc == &OldMathFuncProc) {
OldMathFuncData *dataPtr = cmdPtr->clientData;
*procPtr = dataPtr->proc;
*numArgsPtr = dataPtr->numArgs;
*argTypesPtr = dataPtr->argTypes;
*clientDataPtr = dataPtr->clientData;
} else {
*procPtr = NULL;
*numArgsPtr = -1;
*argTypesPtr = NULL;
*procPtr = NULL;
*clientDataPtr = NULL;
}
return TCL_OK;
}
/*
*----------------------------------------------------------------------
*
* Tcl_ListMathFuncs --
*
* Produces a list of all the math functions defined in a given
* interpreter.
*
* Results:
* A pointer to a Tcl_Obj structure with a reference count of zero, or
* NULL in the case of an error (in which case a suitable error message
* will be left in the interpreter result.)
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
Tcl_Obj *
Tcl_ListMathFuncs(
Tcl_Interp *interp,
const char *pattern)
{
Tcl_Obj *script = Tcl_NewStringObj("::info functions ", -1);
Tcl_Obj *result;
Tcl_InterpState state;
if (pattern) {
Tcl_Obj *patternObj = Tcl_NewStringObj(pattern, -1);
Tcl_Obj *arg = Tcl_NewListObj(1, &patternObj);
Tcl_AppendObjToObj(script, arg);
Tcl_DecrRefCount(arg); /* Should tear down patternObj too */
}
state = Tcl_SaveInterpState(interp, TCL_OK);
Tcl_IncrRefCount(script);
if (TCL_OK == Tcl_EvalObjEx(interp, script, 0)) {
result = Tcl_DuplicateObj(Tcl_GetObjResult(interp));
} else {
result = Tcl_NewObj();
}
Tcl_DecrRefCount(script);
Tcl_RestoreInterpState(interp, state);
return result;
}
/*
*----------------------------------------------------------------------
*
* TclInterpReady --
*
* Check if an interpreter is ready to eval commands or scripts, i.e., if
* it was not deleted and if the nesting level is not too high.
*
* Results:
* The return value is TCL_OK if it the interpreter is ready, TCL_ERROR
* otherwise.
*
* Side effects:
* The interpreters object and string results are cleared.
*
*----------------------------------------------------------------------
*/
int
TclInterpReady(
Tcl_Interp *interp)
{
Interp *iPtr = (Interp *) interp;
/*
* Reset both the interpreter's string and object results and clear out
* any previous error information.
*/
Tcl_ResetResult(interp);
/*
* If the interpreter has been deleted, return an error.
*/
if (iPtr->flags & DELETED) {
Tcl_SetObjResult(interp, Tcl_NewStringObj(
"attempt to call eval in deleted interpreter", -1));
Tcl_SetErrorCode(interp, "TCL", "IDELETE",
"attempt to call eval in deleted interpreter", NULL);
return TCL_ERROR;
}
if (iPtr->execEnvPtr->rewind) {
return TCL_ERROR;
}
/*
* Make sure the script being evaluated (if any) has not been canceled.
*/
if (TclCanceled(iPtr) &&
(TCL_OK != Tcl_Canceled(interp, TCL_LEAVE_ERR_MSG))) {
return TCL_ERROR;
}
/*
* Check depth of nested calls to Tcl_Eval: if this gets too large, it's
* probably because of an infinite loop somewhere.
*/
if (((iPtr->numLevels) <= iPtr->maxNestingDepth)) {
return TCL_OK;
}
Tcl_SetObjResult(interp, Tcl_NewStringObj(
"too many nested evaluations (infinite loop?)", -1));
Tcl_SetErrorCode(interp, "TCL", "LIMIT", "STACK", NULL);
return TCL_ERROR;
}
/*
*----------------------------------------------------------------------
*
* TclResetCancellation --
*
* Reset the script cancellation flags if the nesting level
* (iPtr->numLevels) for the interp is zero or argument force is
* non-zero.
*
* Results:
* A standard Tcl result.
*
* Side effects:
* The script cancellation flags for the interp may be reset.
*
*----------------------------------------------------------------------
*/
int
TclResetCancellation(
Tcl_Interp *interp,
int force)
{
Interp *iPtr = (Interp *) interp;
if (iPtr == NULL) {
return TCL_ERROR;
}
if (force || (iPtr->numLevels == 0)) {
TclUnsetCancelFlags(iPtr);
}
return TCL_OK;
}
/*
*----------------------------------------------------------------------
*
* Tcl_Canceled --
*
* Check if the script in progress has been canceled, i.e.,
* Tcl_CancelEval was called for this interpreter or any of its parent
* interpreters.
*
* Results:
* The return value is TCL_OK if the script evaluation has not been
* canceled, TCL_ERROR otherwise.
*
* If "flags" contains TCL_LEAVE_ERR_MSG, an error message is returned in
* the interpreter's result object. Otherwise, the interpreter's result
* object is left unchanged. If "flags" contains TCL_CANCEL_UNWIND,
* TCL_ERROR will only be returned if the script evaluation is being
* completely unwound.
*
* Side effects:
* The CANCELED flag for the interp will be reset if it is set.
*
*----------------------------------------------------------------------
*/
int
Tcl_Canceled(
Tcl_Interp *interp,
int flags)
{
Interp *iPtr = (Interp *) interp;
/*
* Has the current script in progress for this interpreter been canceled
* or is the stack being unwound due to the previous script cancellation?
*/
if (!TclCanceled(iPtr)) {
return TCL_OK;
}
/*
* The CANCELED flag is a one-shot flag that is reset immediately upon
* being detected; however, if the TCL_CANCEL_UNWIND flag is set we will
* continue to report that the script in progress has been canceled
* thereby allowing the evaluation stack for the interp to be fully
* unwound.
*/
iPtr->flags &= ~CANCELED;
/*
* The CANCELED flag was detected and reset; however, if the caller
* specified the TCL_CANCEL_UNWIND flag, we only return TCL_ERROR
* (indicating that the script in progress has been canceled) if the
* evaluation stack for the interp is being fully unwound.
*/
if ((flags & TCL_CANCEL_UNWIND) && !(iPtr->flags & TCL_CANCEL_UNWIND)) {
return TCL_OK;
}
/*
* If the TCL_LEAVE_ERR_MSG flags bit is set, place an error in the
* interp's result; otherwise, we leave it alone.
*/
if (flags & TCL_LEAVE_ERR_MSG) {
const char *id, *message = NULL;
int length;
/*
* Setup errorCode variables so that we can differentiate between
* being canceled and unwound.
*/
if (iPtr->asyncCancelMsg != NULL) {
message = Tcl_GetStringFromObj(iPtr->asyncCancelMsg, &length);
} else {
length = 0;
}
if (iPtr->flags & TCL_CANCEL_UNWIND) {
id = "IUNWIND";
if (length == 0) {
message = "eval unwound";
}
} else {
id = "ICANCEL";
if (length == 0) {
message = "eval canceled";
}
}
Tcl_SetObjResult(interp, Tcl_NewStringObj(message, -1));
Tcl_SetErrorCode(interp, "TCL", "CANCEL", id, message, NULL);
}
/*
* Return TCL_ERROR to the caller (not necessarily just the Tcl core
* itself) that indicates further processing of the script or command in
* progress should halt gracefully and as soon as possible.
*/
return TCL_ERROR;
}
/*
*----------------------------------------------------------------------
*
* Tcl_CancelEval --
*
* This function schedules the cancellation of the current script in the
* given interpreter.
*
* Results:
* The return value is a standard Tcl completion code such as TCL_OK or
* TCL_ERROR. Since the interp may belong to a different thread, no error
* message can be left in the interp's result.
*
* Side effects:
* The script in progress in the specified interpreter will be canceled
* with TCL_ERROR after asynchronous handlers are invoked at the next
* Tcl_Canceled check.
*
*----------------------------------------------------------------------
*/
int
Tcl_CancelEval(
Tcl_Interp *interp, /* Interpreter in which to cancel the
* script. */
Tcl_Obj *resultObjPtr, /* The script cancellation error message or
* NULL for a default error message. */
ClientData clientData, /* Passed to CancelEvalProc. */
int flags) /* Collection of OR-ed bits that control
* the cancellation of the script. Only
* TCL_CANCEL_UNWIND is currently
* supported. */
{
Tcl_HashEntry *hPtr;
CancelInfo *cancelInfo;
int code = TCL_ERROR;
const char *result;
if (interp == NULL) {
return TCL_ERROR;
}
Tcl_MutexLock(&cancelLock);
if (cancelTableInitialized != 1) {
/*
* No CancelInfo hash table (Tcl_CreateInterp has never been called?)
*/
goto done;
}
hPtr = Tcl_FindHashEntry(&cancelTable, (char *) interp);
if (hPtr == NULL) {
/*
* No CancelInfo record for this interpreter.
*/
goto done;
}
cancelInfo = Tcl_GetHashValue(hPtr);
/*
* Populate information needed by the interpreter thread to fulfill the
* cancellation request. Currently, clientData is ignored. If the
* TCL_CANCEL_UNWIND flags bit is set, the script in progress is not
* allowed to catch the script cancellation because the evaluation stack
* for the interp is completely unwound.
*/
if (resultObjPtr != NULL) {
result = Tcl_GetStringFromObj(resultObjPtr, &cancelInfo->length);
cancelInfo->result = ckrealloc(cancelInfo->result,cancelInfo->length);
memcpy(cancelInfo->result, result, cancelInfo->length);
TclDecrRefCount(resultObjPtr); /* Discard their result object. */
} else {
cancelInfo->result = NULL;
cancelInfo->length = 0;
}
cancelInfo->clientData = clientData;
cancelInfo->flags = flags;
Tcl_AsyncMark(cancelInfo->async);
code = TCL_OK;
done:
Tcl_MutexUnlock(&cancelLock);
return code;
}
/*
*----------------------------------------------------------------------
*
* Tcl_InterpActive --
*
* Returns non-zero if the specified interpreter is in use, i.e. if there
* is an evaluation currently active in the interpreter.
*
* Results:
* See above.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
int
Tcl_InterpActive(
Tcl_Interp *interp)
{
return ((Interp *) interp)->numLevels > 0;
}
/*
*----------------------------------------------------------------------
*
* Tcl_EvalObjv --
*
* This function evaluates a Tcl command that has already been parsed
* into words, with one Tcl_Obj holding each word.
*
* Results:
* The return value is a standard Tcl completion code such as TCL_OK or
* TCL_ERROR. A result or error message is left in interp's result.
*
* Side effects:
* Always pushes a callback. Other side effects depend on the command.
*
*----------------------------------------------------------------------
*/
int
Tcl_EvalObjv(
Tcl_Interp *interp, /* Interpreter in which to evaluate the
* command. Also used for error reporting. */
int objc, /* Number of words in command. */
Tcl_Obj *const objv[], /* An array of pointers to objects that are
* the words that make up the command. */
int flags) /* Collection of OR-ed bits that control the
* evaluation of the script. Only
* TCL_EVAL_GLOBAL, TCL_EVAL_INVOKE and
* TCL_EVAL_NOERR are currently supported. */
{
int result;
NRE_callback *rootPtr = TOP_CB(interp);
result = TclNREvalObjv(interp, objc, objv, flags, NULL);
return TclNRRunCallbacks(interp, result, rootPtr);
}
int
TclNREvalObjv(
Tcl_Interp *interp, /* Interpreter in which to evaluate the
* command. Also used for error reporting. */
int objc, /* Number of words in command. */
Tcl_Obj *const objv[], /* An array of pointers to objects that are
* the words that make up the command. */
int flags, /* Collection of OR-ed bits that control the
* evaluation of the script. Only
* TCL_EVAL_GLOBAL, TCL_EVAL_INVOKE and
* TCL_EVAL_NOERR are currently supported. */
Command *cmdPtr) /* NULL if the Command is to be looked up
* here, otherwise the pointer to the
* requested Command struct to be invoked. */
{
Interp *iPtr = (Interp *) interp;
/*
* data[1] stores a marker for use by tailcalls; it will be set to 1 by
* command redirectors (imports, alias, ensembles) so that tailcall skips
* this callback (that marks the end of the target command) and goes back
* to the end of the source command.
*/
if (iPtr->deferredCallbacks) {
iPtr->deferredCallbacks = NULL;
} else {
TclNRAddCallback(interp, NRCommand, NULL, NULL, NULL, NULL);
}
iPtr->numLevels++;
TclNRAddCallback(interp, EvalObjvCore, cmdPtr, INT2PTR(flags),
INT2PTR(objc), objv);
return TCL_OK;
}
static int
EvalObjvCore(
ClientData data[],
Tcl_Interp *interp,
int result)
{
Command *cmdPtr = NULL, *preCmdPtr = data[0];
int flags = PTR2INT(data[1]);
int objc = PTR2INT(data[2]);
Tcl_Obj **objv = data[3];
Interp *iPtr = (Interp *) interp;
Namespace *lookupNsPtr = NULL;
int enterTracesDone = 0;
/*
* Push records for task to be done on return, in INVERSE order. First, if
* needed, the exception handlers (as they should happen last).
*/
if (!(flags & TCL_EVAL_NOERR)) {
TEOV_PushExceptionHandlers(interp, objc, objv, flags);
}
if (TCL_OK != TclInterpReady(interp)) {
return TCL_ERROR;
}
if (objc == 0) {
return TCL_OK;
}
if (TclLimitExceeded(iPtr->limit)) {
return TCL_ERROR;
}
/*
* Configure evaluation context to match the requested flags.
*/
if (iPtr->lookupNsPtr) {
/*
* Capture the namespace we should do command name resolution in, as
* instructed by our caller sneaking it in to us in a private interp
* field. Clear that field right away so we cannot possibly have its
* use leak where it should not. The sneaky message pass is done.
*
* Use of this mechanism overrides the TCL_EVAL_GLOBAL flag.
* TODO: Is that a bug?
*/
lookupNsPtr = iPtr->lookupNsPtr;
iPtr->lookupNsPtr = NULL;
} else if (flags & TCL_EVAL_INVOKE) {
lookupNsPtr = iPtr->globalNsPtr;
} else {
/*
* TCL_EVAL_INVOKE was not set: clear rewrite rules
*/
TclResetRewriteEnsemble(interp, 1);
if (flags & TCL_EVAL_GLOBAL) {
TEOV_SwitchVarFrame(interp);
lookupNsPtr = iPtr->globalNsPtr;
}
}
/*
* Lookup the Command to dispatch.
*/
reresolve:
assert(cmdPtr == NULL);
if (preCmdPtr) {
/*
* Caller gave it to us.
*/
if (!(preCmdPtr->flags & CMD_DEAD)) {
/*
* So long as it exists, use it.
*/
cmdPtr = preCmdPtr;
} else if (flags & TCL_EVAL_NORESOLVE) {
/*
* When it's been deleted, and we're told not to attempt resolving
* it ourselves, all we can do is raise an error.
*/
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
"attempt to invoke a deleted command"));
Tcl_SetErrorCode(interp, "TCL", "EVAL", "DELETEDCOMMAND", NULL);
return TCL_ERROR;
}
}
if (cmdPtr == NULL) {
cmdPtr = TEOV_LookupCmdFromObj(interp, objv[0], lookupNsPtr);
if (!cmdPtr) {
return TEOV_NotFound(interp, objc, objv, lookupNsPtr);
}
}
if (enterTracesDone || iPtr->tracePtr
|| (cmdPtr->flags & CMD_HAS_EXEC_TRACES)) {
Tcl_Obj *commandPtr = TclGetSourceFromFrame(
flags & TCL_EVAL_SOURCE_IN_FRAME ? iPtr->cmdFramePtr : NULL,
objc, objv);
Tcl_IncrRefCount(commandPtr);
if (!enterTracesDone) {
int code = TEOV_RunEnterTraces(interp, &cmdPtr, commandPtr,
objc, objv);
/*
* Send any exception from enter traces back as an exception
* raised by the traced command.
* TODO: Is this a bug? Letting an execution trace BREAK or
* CONTINUE or RETURN in the place of the traced command? Would
* either converting all exceptions to TCL_ERROR, or just
* swallowing them be better? (Swallowing them has the problem of
* permanently hiding program errors.)
*/
if (code != TCL_OK) {
Tcl_DecrRefCount(commandPtr);
return code;
}
/*
* If the enter traces made the resolved cmdPtr unusable, go back
* and resolve again, but next time don't run enter traces again.
*/
if (cmdPtr == NULL) {
enterTracesDone = 1;
Tcl_DecrRefCount(commandPtr);
goto reresolve;
}
}
/*
* Schedule leave traces. Raise the refCount on the resolved cmdPtr,
* so that when it passes to the leave traces we know it's still
* valid.
*/
cmdPtr->refCount++;
TclNRAddCallback(interp, TEOV_RunLeaveTraces, INT2PTR(objc),
commandPtr, cmdPtr, objv);
}
TclNRAddCallback(interp, Dispatch,
cmdPtr->nreProc ? cmdPtr->nreProc : cmdPtr->objProc,
cmdPtr->objClientData, INT2PTR(objc), objv);
return TCL_OK;
}
static int
Dispatch(
ClientData data[],
Tcl_Interp *interp,
int result)
{
Tcl_ObjCmdProc *objProc = data[0];
ClientData clientData = data[1];
int objc = PTR2INT(data[2]);
Tcl_Obj **objv = data[3];
Interp *iPtr = (Interp *) interp;
#ifdef USE_DTRACE
if (TCL_DTRACE_CMD_ARGS_ENABLED()) {
const char *a[10];
int i = 0;
while (i < 10) {
a[i] = i < objc ? TclGetString(objv[i]) : NULL; i++;
}
TCL_DTRACE_CMD_ARGS(a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7],
a[8], a[9]);
}
if (TCL_DTRACE_CMD_INFO_ENABLED() && iPtr->cmdFramePtr) {
Tcl_Obj *info = TclInfoFrame(interp, iPtr->cmdFramePtr);
const char *a[6]; int i[2];
TclDTraceInfo(info, a, i);
TCL_DTRACE_CMD_INFO(a[0], a[1], a[2], a[3], i[0], i[1], a[4], a[5]);
TclDecrRefCount(info);
}
if ((TCL_DTRACE_CMD_RETURN_ENABLED() || TCL_DTRACE_CMD_RESULT_ENABLED())
&& objc) {
TclNRAddCallback(interp, DTraceCmdReturn, objv[0], NULL, NULL, NULL);
}
if (TCL_DTRACE_CMD_ENTRY_ENABLED() && objc) {
TCL_DTRACE_CMD_ENTRY(TclGetString(objv[0]), objc - 1,
(Tcl_Obj **)(objv + 1));
}
#endif /* USE_DTRACE */
iPtr->cmdCount++;
return objProc(clientData, interp, objc, objv);
}
int
TclNRRunCallbacks(
Tcl_Interp *interp,
int result,
struct NRE_callback *rootPtr)
/* All callbacks down to rootPtr not inclusive
* are to be run. */
{
Interp *iPtr = (Interp *) interp;
/*
* If the interpreter has a non-empty string result, the result object is
* either empty or stale because some function set interp->result
* directly. If so, move the string result to the result object, then
* reset the string result.
*
* This only needs to be done for the first item in the list: all other
* are for NR function calls, and those are Tcl_Obj based.
*/
if (*(iPtr->result) != 0) {
(void) Tcl_GetObjResult(interp);
}
/*
* This is the trampoline.
*/
while (TOP_CB(interp) != rootPtr) {
NRE_callback *callbackPtr = TOP_CB(interp);
Tcl_NRPostProc *procPtr = callbackPtr->procPtr;
TOP_CB(interp) = callbackPtr->nextPtr;
result = procPtr(callbackPtr->data, interp, result);
TCLNR_FREE(interp, callbackPtr);
}
return result;
}
static int
NRCommand(
ClientData data[],
Tcl_Interp *interp,
int result)
{
Interp *iPtr = (Interp *) interp;
iPtr->numLevels--;
/*
* If there is a tailcall, schedule it next
*/
if (data[1] && (data[1] != INT2PTR(1))) {
TclNRAddCallback(interp, TclNRTailcallEval, data[1], NULL, NULL, NULL);
}
/* OPT ??
* Do not interrupt a series of cleanups with async or limit checks:
* just check at the end?
*/
if (TclAsyncReady(iPtr)) {
result = Tcl_AsyncInvoke(interp, result);
}
if ((result == TCL_OK) && TclCanceled(iPtr)) {
result = Tcl_Canceled(interp, TCL_LEAVE_ERR_MSG);
}
if (result == TCL_OK && TclLimitReady(iPtr->limit)) {
result = Tcl_LimitCheck(interp);
}
return result;
}
/*
*----------------------------------------------------------------------
*
* TEOV_Exception -
* TEOV_LookupCmdFromObj -
* TEOV_RunEnterTraces -
* TEOV_RunLeaveTraces -
* TEOV_NotFound -
*
* These are helper functions for Tcl_EvalObjv.
*
*----------------------------------------------------------------------
*/
static void
TEOV_PushExceptionHandlers(
Tcl_Interp *interp,
int objc,
Tcl_Obj *const objv[],
int flags)
{
Interp *iPtr = (Interp *) interp;
/*
* If any error processing is necessary, push the appropriate records.
* Note that we have to push them in the inverse order: first the one that
* has to run last.
*/
if (!(flags & TCL_EVAL_INVOKE)) {
/*
* Error messages
*/
TclNRAddCallback(interp, TEOV_Error, INT2PTR(objc),
(ClientData) objv, NULL, NULL);
}
if (iPtr->numLevels == 1) {
/*
* No CONTINUE or BREAK at level 0, manage RETURN
*/
TclNRAddCallback(interp, TEOV_Exception, INT2PTR(iPtr->evalFlags),
NULL, NULL, NULL);
}
}
static void
TEOV_SwitchVarFrame(
Tcl_Interp *interp)
{
Interp *iPtr = (Interp *) interp;
/*
* Change the varFrame to be the rootVarFrame, and push a record to
* restore things at the end.
*/
TclNRAddCallback(interp, TEOV_RestoreVarFrame, iPtr->varFramePtr, NULL,
NULL, NULL);
iPtr->varFramePtr = iPtr->rootFramePtr;
}
static int
TEOV_RestoreVarFrame(
ClientData data[],
Tcl_Interp *interp,
int result)
{
((Interp *) interp)->varFramePtr = data[0];
return result;
}
static int
TEOV_Exception(
ClientData data[],
Tcl_Interp *interp,
int result)
{
Interp *iPtr = (Interp *) interp;
int allowExceptions = (PTR2INT(data[0]) & TCL_ALLOW_EXCEPTIONS);
if (result != TCL_OK) {
if (result == TCL_RETURN) {
result = TclUpdateReturnInfo(iPtr);
}
if ((result != TCL_OK) && (result != TCL_ERROR) && !allowExceptions) {
ProcessUnexpectedResult(interp, result);
result = TCL_ERROR;
}
}
/*
* We are returning to level 0, so should process TclResetCancellation. As
* numLevels has not *yet* been decreased, do not call it: do the thing
* here directly.
*/
TclUnsetCancelFlags(iPtr);
return result;
}
static int
TEOV_Error(
ClientData data[],
Tcl_Interp *interp,
int result)
{
Interp *iPtr = (Interp *) interp;
Tcl_Obj *listPtr;
const char *cmdString;
int cmdLen;
int objc = PTR2INT(data[0]);
Tcl_Obj **objv = data[1];
if ((result == TCL_ERROR) && !(iPtr->flags & ERR_ALREADY_LOGGED)){
/*
* If there was an error, a command string will be needed for the
* error log: get it out of the itemPtr. The details depend on the
* type.
*/
listPtr = Tcl_NewListObj(objc, objv);
cmdString = Tcl_GetStringFromObj(listPtr, &cmdLen);
Tcl_LogCommandInfo(interp, cmdString, cmdString, cmdLen);
Tcl_DecrRefCount(listPtr);
}
iPtr->flags &= ~ERR_ALREADY_LOGGED;
return result;
}
static int
TEOV_NotFound(
Tcl_Interp *interp,
int objc,
Tcl_Obj *const objv[],
Namespace *lookupNsPtr)
{
Command * cmdPtr;
Interp *iPtr = (Interp *) interp;
int i, newObjc, handlerObjc;
Tcl_Obj **newObjv, **handlerObjv;
CallFrame *varFramePtr = iPtr->varFramePtr;
Namespace *currNsPtr = NULL;/* Used to check for and invoke any registered
* unknown command handler for the current
* namespace (TIP 181). */
Namespace *savedNsPtr = NULL;
currNsPtr = varFramePtr->nsPtr;
if ((currNsPtr == NULL) || (currNsPtr->unknownHandlerPtr == NULL)) {
currNsPtr = iPtr->globalNsPtr;
if (currNsPtr == NULL) {
Tcl_Panic("Tcl_EvalObjv: NULL global namespace pointer");
}
}
/*
* Check to see if the resolution namespace has lost its unknown handler.
* If so, reset it to "::unknown".
*/
if (currNsPtr->unknownHandlerPtr == NULL) {
TclNewLiteralStringObj(currNsPtr->unknownHandlerPtr, "::unknown");
Tcl_IncrRefCount(currNsPtr->unknownHandlerPtr);
}
/*
* Get the list of words for the unknown handler and allocate enough space
* to hold both the handler prefix and all words of the command invokation
* itself.
*/
Tcl_ListObjGetElements(NULL, currNsPtr->unknownHandlerPtr,
&handlerObjc, &handlerObjv);
newObjc = objc + handlerObjc;
newObjv = TclStackAlloc(interp, (int) sizeof(Tcl_Obj *) * newObjc);
/*
* Copy command prefix from unknown handler and add on the real command's
* full argument list. Note that we only use memcpy() once because we have
* to increment the reference count of all the handler arguments anyway.
*/
for (i = 0; i < handlerObjc; ++i) {
newObjv[i] = handlerObjv[i];
Tcl_IncrRefCount(newObjv[i]);
}
memcpy(newObjv+handlerObjc, objv, sizeof(Tcl_Obj *) * objc);
/*
* Look up and invoke the handler (by recursive call to this function). If
* there is no handler at all, instead of doing the recursive call we just
* generate a generic error message; it would be an infinite-recursion
* nightmare otherwise.
*
* In this case we worry a bit less about recursion for now, and call the
* "blocking" interface.
*/
cmdPtr = TEOV_LookupCmdFromObj(interp, newObjv[0], lookupNsPtr);
if (cmdPtr == NULL) {
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
"invalid command name \"%s\"", TclGetString(objv[0])));
Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "COMMAND",
TclGetString(objv[0]), NULL);
/*
* Release any resources we locked and allocated during the handler
* call.
*/
for (i = 0; i < handlerObjc; ++i) {
Tcl_DecrRefCount(newObjv[i]);
}
TclStackFree(interp, newObjv);
return TCL_ERROR;
}
if (lookupNsPtr) {
savedNsPtr = varFramePtr->nsPtr;
varFramePtr->nsPtr = lookupNsPtr;
}
TclSkipTailcall(interp);
TclNRAddCallback(interp, TEOV_NotFoundCallback, INT2PTR(handlerObjc),
newObjv, savedNsPtr, NULL);
return TclNREvalObjv(interp, newObjc, newObjv, TCL_EVAL_NOERR, NULL);
}
static int
TEOV_NotFoundCallback(
ClientData data[],
Tcl_Interp *interp,
int result)
{
Interp *iPtr = (Interp *) interp;
int objc = PTR2INT(data[0]);
Tcl_Obj **objv = data[1];
Namespace *savedNsPtr = data[2];
int i;
if (savedNsPtr) {
iPtr->varFramePtr->nsPtr = savedNsPtr;
}
/*
* Release any resources we locked and allocated during the handler call.
*/
for (i = 0; i < objc; ++i) {
Tcl_DecrRefCount(objv[i]);
}
TclStackFree(interp, objv);
return result;
}
static int
TEOV_RunEnterTraces(
Tcl_Interp *interp,
Command **cmdPtrPtr,
Tcl_Obj *commandPtr,
int objc,
Tcl_Obj *const objv[])
{
Interp *iPtr = (Interp *) interp;
Command *cmdPtr = *cmdPtrPtr;
int newEpoch, cmdEpoch = cmdPtr->cmdEpoch;
int length, traceCode = TCL_OK;
const char *command = Tcl_GetStringFromObj(commandPtr, &length);
/*
* Call trace functions.
* Execute any command or execution traces. Note that we bump up the
* command's reference count for the duration of the calling of the
* traces so that the structure doesn't go away underneath our feet.
*/
cmdPtr->refCount++;
if (iPtr->tracePtr) {
traceCode = TclCheckInterpTraces(interp, command, length,
cmdPtr, TCL_OK, TCL_TRACE_ENTER_EXEC, objc, objv);
}
if ((cmdPtr->flags & CMD_HAS_EXEC_TRACES) && (traceCode == TCL_OK)) {
traceCode = TclCheckExecutionTraces(interp, command, length,
cmdPtr, TCL_OK, TCL_TRACE_ENTER_EXEC, objc, objv);
}
newEpoch = cmdPtr->cmdEpoch;
TclCleanupCommandMacro(cmdPtr);
if (traceCode != TCL_OK) {
if (traceCode == TCL_ERROR) {
Tcl_Obj *info;
TclNewLiteralStringObj(info, "\n (enter trace on \"");
Tcl_AppendLimitedToObj(info, command, length, 55, "...");
Tcl_AppendToObj(info, "\")", 2);
Tcl_AppendObjToErrorInfo(interp, info);
iPtr->flags |= ERR_ALREADY_LOGGED;
}
return traceCode;
}
if (cmdEpoch != newEpoch) {
*cmdPtrPtr = NULL;
}
return TCL_OK;
}
static int
TEOV_RunLeaveTraces(
ClientData data[],
Tcl_Interp *interp,
int result)
{
Interp *iPtr = (Interp *) interp;
int traceCode = TCL_OK;
int objc = PTR2INT(data[0]);
Tcl_Obj *commandPtr = data[1];
Command *cmdPtr = data[2];
Tcl_Obj **objv = data[3];
int length;
const char *command = Tcl_GetStringFromObj(commandPtr, &length);
if (!(cmdPtr->flags & CMD_IS_DELETED)) {
if (cmdPtr->flags & CMD_HAS_EXEC_TRACES){
traceCode = TclCheckExecutionTraces(interp, command, length,
cmdPtr, result, TCL_TRACE_LEAVE_EXEC, objc, objv);
}
if (iPtr->tracePtr != NULL && traceCode == TCL_OK) {
traceCode = TclCheckInterpTraces(interp, command, length,
cmdPtr, result, TCL_TRACE_LEAVE_EXEC, objc, objv);
}
}
/*
* As cmdPtr is set, TclNRRunCallbacks is about to reduce the numlevels.
* Prevent that by resetting the cmdPtr field and dealing right here with
* cmdPtr->refCount.
*/
TclCleanupCommandMacro(cmdPtr);
if (traceCode != TCL_OK) {
if (traceCode == TCL_ERROR) {
Tcl_Obj *info;
TclNewLiteralStringObj(info, "\n (leave trace on \"");
Tcl_AppendLimitedToObj(info, command, length, 55, "...");
Tcl_AppendToObj(info, "\")", 2);
Tcl_AppendObjToErrorInfo(interp, info);
iPtr->flags |= ERR_ALREADY_LOGGED;
}
result = traceCode;
}
Tcl_DecrRefCount(commandPtr);
return result;
}
static inline Command *
TEOV_LookupCmdFromObj(
Tcl_Interp *interp,
Tcl_Obj *namePtr,
Namespace *lookupNsPtr)
{
Interp *iPtr = (Interp *) interp;
Command *cmdPtr;
Namespace *savedNsPtr = iPtr->varFramePtr->nsPtr;
if (lookupNsPtr) {
iPtr->varFramePtr->nsPtr = lookupNsPtr;
}
cmdPtr = (Command *) Tcl_GetCommandFromObj(interp, namePtr);
iPtr->varFramePtr->nsPtr = savedNsPtr;
return cmdPtr;
}
/*
*----------------------------------------------------------------------
*
* Tcl_EvalTokensStandard --
*
* Given an array of tokens parsed from a Tcl command (e.g., the tokens
* that make up a word or the index for an array variable) this function
* evaluates the tokens and concatenates their values to form a single
* result value.
*
* Results:
* The return value is a standard Tcl completion code such as TCL_OK or
* TCL_ERROR. A result or error message is left in interp's result.
*
* Side effects:
* Depends on the array of tokens being evaled.
*
*----------------------------------------------------------------------
*/
int
Tcl_EvalTokensStandard(
Tcl_Interp *interp, /* Interpreter in which to lookup variables,
* execute nested commands, and report
* errors. */
Tcl_Token *tokenPtr, /* Pointer to first in an array of tokens to
* evaluate and concatenate. */
int count) /* Number of tokens to consider at tokenPtr.
* Must be at least 1. */
{
return TclSubstTokens(interp, tokenPtr, count, /* numLeftPtr */ NULL, 1,
NULL, NULL);
}
/*
*----------------------------------------------------------------------
*
* Tcl_EvalTokens --
*
* Given an array of tokens parsed from a Tcl command (e.g., the tokens
* that make up a word or the index for an array variable) this function
* evaluates the tokens and concatenates their values to form a single
* result value.
*
* Results:
* The return value is a pointer to a newly allocated Tcl_Obj containing
* the value of the array of tokens. The reference count of the returned
* object has been incremented. If an error occurs in evaluating the
* tokens then a NULL value is returned and an error message is left in
* interp's result.
*
* Side effects:
* A new object is allocated to hold the result.
*
*----------------------------------------------------------------------
*
* This uses a non-standard return convention; its use is now deprecated. It
* is a wrapper for the new function Tcl_EvalTokensStandard, and is not used
* in the core any longer. It is only kept for backward compatibility.
*/
Tcl_Obj *
Tcl_EvalTokens(
Tcl_Interp *interp, /* Interpreter in which to lookup variables,
* execute nested commands, and report
* errors. */
Tcl_Token *tokenPtr, /* Pointer to first in an array of tokens to
* evaluate and concatenate. */
int count) /* Number of tokens to consider at tokenPtr.
* Must be at least 1. */
{
Tcl_Obj *resPtr;
if (Tcl_EvalTokensStandard(interp, tokenPtr, count) != TCL_OK) {
return NULL;
}
resPtr = Tcl_GetObjResult(interp);
Tcl_IncrRefCount(resPtr);
Tcl_ResetResult(interp);
return resPtr;
}
/*
*----------------------------------------------------------------------
*
* Tcl_EvalEx, TclEvalEx --
*
* This function evaluates a Tcl script without using the compiler or
* byte-code interpreter. It just parses the script, creates values for
* each word of each command, then calls EvalObjv to execute each
* command.
*
* Results:
* The return value is a standard Tcl completion code such as TCL_OK or
* TCL_ERROR. A result or error message is left in interp's result.
*
* Side effects:
* Depends on the script.
*
* TIP #280 : Keep public API, internally extended API.
*----------------------------------------------------------------------
*/
int
Tcl_EvalEx(
Tcl_Interp *interp, /* Interpreter in which to evaluate the
* script. Also used for error reporting. */
const char *script, /* First character of script to evaluate. */
int numBytes, /* Number of bytes in script. If < 0, the
* script consists of all bytes up to the
* first null character. */
int flags) /* Collection of OR-ed bits that control the
* evaluation of the script. Only
* TCL_EVAL_GLOBAL is currently supported. */
{
return TclEvalEx(interp, script, numBytes, flags, 1, NULL, script);
}
int
TclEvalEx(
Tcl_Interp *interp, /* Interpreter in which to evaluate the
* script. Also used for error reporting. */
const char *script, /* First character of script to evaluate. */
int numBytes, /* Number of bytes in script. If < 0, the
* script consists of all bytes up to the
* first NUL character. */
int flags, /* Collection of OR-ed bits that control the
* evaluation of the script. Only
* TCL_EVAL_GLOBAL is currently supported. */
int line, /* The line the script starts on. */
int *clNextOuter, /* Information about an outer context for */
const char *outerScript) /* continuation line data. This is set only in
* TclSubstTokens(), to properly handle
* [...]-nested commands. The 'outerScript'
* refers to the most-outer script containing
* the embedded command, which is refered to
* by 'script'. The 'clNextOuter' refers to
* the current entry in the table of
* continuation lines in this "main script",
* and the character offsets are relative to
* the 'outerScript' as well.
*
* If outerScript == script, then this call is
* for the outer-most script/command. See
* Tcl_EvalEx() and TclEvalObjEx() for places
* generating arguments for which this is
* true. */
{
Interp *iPtr = (Interp *) interp;
const char *p, *next;
const unsigned int minObjs = 20;
Tcl_Obj **objv, **objvSpace;
int *expand, *lines, *lineSpace;
Tcl_Token *tokenPtr;
int commandLength, bytesLeft, expandRequested, code = TCL_OK;
CallFrame *savedVarFramePtr;/* Saves old copy of iPtr->varFramePtr in case
* TCL_EVAL_GLOBAL was set. */
int allowExceptions = (iPtr->evalFlags & TCL_ALLOW_EXCEPTIONS);
int gotParse = 0;
unsigned int i, objectsUsed = 0;
/* These variables keep track of how much
* state has been allocated while evaluating
* the script, so that it can be freed
* properly if an error occurs. */
Tcl_Parse *parsePtr = TclStackAlloc(interp, sizeof(Tcl_Parse));
CmdFrame *eeFramePtr = TclStackAlloc(interp, sizeof(CmdFrame));
Tcl_Obj **stackObjArray =
TclStackAlloc(interp, minObjs * sizeof(Tcl_Obj *));
int *expandStack = TclStackAlloc(interp, minObjs * sizeof(int));
int *linesStack = TclStackAlloc(interp, minObjs * sizeof(int));
/* TIP #280 Structures for tracking of command
* locations. */
int *clNext = NULL; /* Pointer for the tracking of invisible
* continuation lines. Initialized only if the
* caller gave us a table of locations to
* track, via scriptCLLocPtr. It always refers
* to the table entry holding the location of
* the next invisible continuation line to
* look for, while parsing the script. */
if (iPtr->scriptCLLocPtr) {
if (clNextOuter) {
clNext = clNextOuter;
} else {
clNext = &iPtr->scriptCLLocPtr->loc[0];
}
}
if (numBytes < 0) {
numBytes = strlen(script);
}
Tcl_ResetResult(interp);
savedVarFramePtr = iPtr->varFramePtr;
if (flags & TCL_EVAL_GLOBAL) {
iPtr->varFramePtr = iPtr->rootFramePtr;
}
/*
* Each iteration through the following loop parses the next command from
* the script and then executes it.
*/
objv = objvSpace = stackObjArray;
lines = lineSpace = linesStack;
expand = expandStack;
p = script;
bytesLeft = numBytes;
/*
* TIP #280 Initialize tracking. Do not push on the frame stack yet.
*
* We open a new context, either for a sourced script, or 'eval'.
* For sourced files we always have a path object, even if nothing was
* specified in the interp itself. That makes code using it simpler as
* NULL checks can be left out. Sourced file without path in the
* 'scriptFile' is possible during Tcl initialization.
*/
eeFramePtr->level = iPtr->cmdFramePtr ? iPtr->cmdFramePtr->level + 1 : 1;
eeFramePtr->framePtr = iPtr->framePtr;
eeFramePtr->nextPtr = iPtr->cmdFramePtr;
eeFramePtr->nline = 0;
eeFramePtr->line = NULL;
eeFramePtr->cmdObj = NULL;
iPtr->cmdFramePtr = eeFramePtr;
if (iPtr->evalFlags & TCL_EVAL_FILE) {
/*
* Set up for a sourced file.
*/
eeFramePtr->type = TCL_LOCATION_SOURCE;
if (iPtr->scriptFile) {
/*
* Normalization here, to have the correct pwd. Should have
* negligible impact on performance, as the norm should have been
* done already by the 'source' invoking us, and it caches the
* result.
*/
Tcl_Obj *norm = Tcl_FSGetNormalizedPath(interp, iPtr->scriptFile);
if (norm == NULL) {
/*
* Error message in the interp result.
*/
code = TCL_ERROR;
goto error;
}
eeFramePtr->data.eval.path = norm;
} else {
TclNewLiteralStringObj(eeFramePtr->data.eval.path, "");
}
Tcl_IncrRefCount(eeFramePtr->data.eval.path);
} else {
/*
* Set up for plain eval.
*/
eeFramePtr->type = TCL_LOCATION_EVAL;
eeFramePtr->data.eval.path = NULL;
}
iPtr->evalFlags = 0;
do {
if (Tcl_ParseCommand(interp, p, bytesLeft, 0, parsePtr) != TCL_OK) {
code = TCL_ERROR;
Tcl_LogCommandInfo(interp, script, parsePtr->commandStart,
parsePtr->term + 1 - parsePtr->commandStart);
goto posterror;
}
/*
* TIP #280 Track lines. The parser may have skipped text till it
* found the command we are now at. We have to count the lines in this
* block, and do not forget invisible continuation lines.
*/
TclAdvanceLines(&line, p, parsePtr->commandStart);
TclAdvanceContinuations(&line, &clNext,
parsePtr->commandStart - outerScript);
gotParse = 1;
if (parsePtr->numWords > 0) {
/*
* TIP #280. Track lines within the words of the current
* command. We use a separate pointer into the table of
* continuation line locations to not lose our position for the
* per-command parsing.
*/
int wordLine = line;
const char *wordStart = parsePtr->commandStart;
int *wordCLNext = clNext;
unsigned int objectsNeeded = 0;
unsigned int numWords = parsePtr->numWords;
/*
* Generate an array of objects for the words of the command.
*/
if (numWords > minObjs) {
expand = ckalloc(numWords * sizeof(int));
objvSpace = ckalloc(numWords * sizeof(Tcl_Obj *));
lineSpace = ckalloc(numWords * sizeof(int));
}
expandRequested = 0;
objv = objvSpace;
lines = lineSpace;
iPtr->cmdFramePtr = eeFramePtr->nextPtr;
for (objectsUsed = 0, tokenPtr = parsePtr->tokenPtr;
objectsUsed < numWords;
objectsUsed++, tokenPtr += tokenPtr->numComponents+1) {
/*
* TIP #280. Track lines to current word. Save the information
* on a per-word basis, signaling dynamic words as needed.
* Make the information available to the recursively called
* evaluator as well, including the type of context (source
* vs. eval).
*/
TclAdvanceLines(&wordLine, wordStart, tokenPtr->start);
TclAdvanceContinuations(&wordLine, &wordCLNext,
tokenPtr->start - outerScript);
wordStart = tokenPtr->start;
lines[objectsUsed] = TclWordKnownAtCompileTime(tokenPtr, NULL)
? wordLine : -1;
if (eeFramePtr->type == TCL_LOCATION_SOURCE) {
iPtr->evalFlags |= TCL_EVAL_FILE;
}
code = TclSubstTokens(interp, tokenPtr+1,
tokenPtr->numComponents, NULL, wordLine,
wordCLNext, outerScript);
iPtr->evalFlags = 0;
if (code != TCL_OK) {
break;
}
objv[objectsUsed] = Tcl_GetObjResult(interp);
Tcl_IncrRefCount(objv[objectsUsed]);
if (tokenPtr->type == TCL_TOKEN_EXPAND_WORD) {
int numElements;
code = TclListObjLength(interp, objv[objectsUsed],
&numElements);
if (code == TCL_ERROR) {
/*
* Attempt to expand a non-list.
*/
Tcl_AppendObjToErrorInfo(interp, Tcl_ObjPrintf(
"\n (expanding word %d)", objectsUsed));
Tcl_DecrRefCount(objv[objectsUsed]);
break;
}
expandRequested = 1;
expand[objectsUsed] = 1;
objectsNeeded += (numElements ? numElements : 1);
} else {
expand[objectsUsed] = 0;
objectsNeeded++;
}
if (wordCLNext) {
TclContinuationsEnterDerived(objv[objectsUsed],
wordStart - outerScript, wordCLNext);
}
} /* for loop */
iPtr->cmdFramePtr = eeFramePtr;
if (code != TCL_OK) {
goto error;
}
if (expandRequested) {
/*
* Some word expansion was requested. Check for objv resize.
*/
Tcl_Obj **copy = objvSpace;
int *lcopy = lineSpace;
int wordIdx = numWords;
int objIdx = objectsNeeded - 1;
if ((numWords > minObjs) || (objectsNeeded > minObjs)) {
objv = objvSpace =
ckalloc(objectsNeeded * sizeof(Tcl_Obj *));
lines = lineSpace = ckalloc(objectsNeeded * sizeof(int));
}
objectsUsed = 0;
while (wordIdx--) {
if (expand[wordIdx]) {
int numElements;
Tcl_Obj **elements, *temp = copy[wordIdx];
Tcl_ListObjGetElements(NULL, temp, &numElements,
&elements);
objectsUsed += numElements;
while (numElements--) {
lines[objIdx] = -1;
objv[objIdx--] = elements[numElements];
Tcl_IncrRefCount(elements[numElements]);
}
Tcl_DecrRefCount(temp);
} else {
lines[objIdx] = lcopy[wordIdx];
objv[objIdx--] = copy[wordIdx];
objectsUsed++;
}
}
objv += objIdx+1;
if (copy != stackObjArray) {
ckfree(copy);
}
if (lcopy != linesStack) {
ckfree(lcopy);
}
}
/*
* Execute the command and free the objects for its words.
*
* TIP #280: Remember the command itself for 'info frame'. We
* shorten the visible command by one char to exclude the
* termination character, if necessary. Here is where we put our
* frame on the stack of frames too. _After_ the nested commands
* have been executed.
*/
eeFramePtr->cmd = parsePtr->commandStart;
eeFramePtr->len = parsePtr->commandSize;
if (parsePtr->term ==
parsePtr->commandStart + parsePtr->commandSize - 1) {
eeFramePtr->len--;
}
eeFramePtr->nline = objectsUsed;
eeFramePtr->line = lines;
TclArgumentEnter(interp, objv, objectsUsed, eeFramePtr);
code = Tcl_EvalObjv(interp, objectsUsed, objv,
TCL_EVAL_NOERR | TCL_EVAL_SOURCE_IN_FRAME);
TclArgumentRelease(interp, objv, objectsUsed);
eeFramePtr->line = NULL;
eeFramePtr->nline = 0;
if (eeFramePtr->cmdObj) {
Tcl_DecrRefCount(eeFramePtr->cmdObj);
eeFramePtr->cmdObj = NULL;
}
if (code != TCL_OK) {
goto error;
}
for (i = 0; i < objectsUsed; i++) {
Tcl_DecrRefCount(objv[i]);
}
objectsUsed = 0;
if (objvSpace != stackObjArray) {
ckfree(objvSpace);
objvSpace = stackObjArray;
ckfree(lineSpace);
lineSpace = linesStack;
}
/*
* Free expand separately since objvSpace could have been
* reallocated above.
*/
if (expand != expandStack) {
ckfree(expand);
expand = expandStack;
}
}
/*
* Advance to the next command in the script.
*
* TIP #280 Track Lines. Now we track how many lines were in the
* executed command.
*/
next = parsePtr->commandStart + parsePtr->commandSize;
bytesLeft -= next - p;
p = next;
TclAdvanceLines(&line, parsePtr->commandStart, p);
Tcl_FreeParse(parsePtr);
gotParse = 0;
} while (bytesLeft > 0);
iPtr->varFramePtr = savedVarFramePtr;
code = TCL_OK;
goto cleanup_return;
error:
/*
* Generate and log various pieces of error information.
*/
if (iPtr->numLevels == 0) {
if (code == TCL_RETURN) {
code = TclUpdateReturnInfo(iPtr);
}
if ((code != TCL_OK) && (code != TCL_ERROR) && !allowExceptions) {
ProcessUnexpectedResult(interp, code);
code = TCL_ERROR;
}
}
if ((code == TCL_ERROR) && !(iPtr->flags & ERR_ALREADY_LOGGED)) {
commandLength = parsePtr->commandSize;
if (parsePtr->term == parsePtr->commandStart + commandLength - 1) {
/*
* The terminator character (such as ; or ]) of the command where
* the error occurred is the last character in the parsed command.
* Reduce the length by one so that the error message doesn't
* include the terminator character.
*/
commandLength -= 1;
}
Tcl_LogCommandInfo(interp, script, parsePtr->commandStart,
commandLength);
}
posterror:
iPtr->flags &= ~ERR_ALREADY_LOGGED;
/*
* Then free resources that had been allocated to the command.
*/
for (i = 0; i < objectsUsed; i++) {
Tcl_DecrRefCount(objv[i]);
}
if (gotParse) {
Tcl_FreeParse(parsePtr);
}
if (objvSpace != stackObjArray) {
ckfree(objvSpace);
ckfree(lineSpace);
}
if (expand != expandStack) {
ckfree(expand);
}
iPtr->varFramePtr = savedVarFramePtr;
cleanup_return:
/*
* TIP #280. Release the local CmdFrame, and its contents.
*/
iPtr->cmdFramePtr = iPtr->cmdFramePtr->nextPtr;
if (eeFramePtr->type == TCL_LOCATION_SOURCE) {
Tcl_DecrRefCount(eeFramePtr->data.eval.path);
}
TclStackFree(interp, linesStack);
TclStackFree(interp, expandStack);
TclStackFree(interp, stackObjArray);
TclStackFree(interp, eeFramePtr);
TclStackFree(interp, parsePtr);
return code;
}
/*
*----------------------------------------------------------------------
*
* TclAdvanceLines --
*
* This function is a helper which counts the number of lines in a block
* of text and advances an external counter.
*
* Results:
* None.
*
* Side effects:
* The specified counter is advanced per the number of lines found.
*
* TIP #280
*----------------------------------------------------------------------
*/
void
TclAdvanceLines(
int *line,
const char *start,
const char *end)
{
const char *p;
for (p = start; p < end; p++) {
if (*p == '\n') {
(*line)++;
}
}
}
/*
*----------------------------------------------------------------------
*
* TclAdvanceContinuations --
*
* This procedure is a helper which counts the number of continuation
* lines (CL) in a block of text using a table of CL locations and
* advances an external counter, and the pointer into the table.
*
* Results:
* None.
*
* Side effects:
* The specified counter is advanced per the number of continuation lines
* found.
*
* TIP #280
*----------------------------------------------------------------------
*/
void
TclAdvanceContinuations(
int *line,
int **clNextPtrPtr,
int loc)
{
/*
* Track the invisible continuation lines embedded in a script, if any.
* Here they are just spaces (already). They were removed by
* TclSubstTokens via TclParseBackslash.
*
* *clNextPtrPtr <=> We have continuation lines to track.
* **clNextPtrPtr >= 0 <=> We are not beyond the last possible location.
* loc >= **clNextPtrPtr <=> We stepped beyond the current cont. line.
*/
while (*clNextPtrPtr && (**clNextPtrPtr >= 0)
&& (loc >= **clNextPtrPtr)) {
/*
* We just stepped over an invisible continuation line. Adjust the
* line counter and step to the table entry holding the location of
* the next continuation line to track.
*/
(*line)++;
(*clNextPtrPtr)++;
}
}
/*
*----------------------------------------------------------------------
* Note: The whole data structure access for argument location tracking is
* hidden behind these three functions. The only parts open are the lineLAPtr
* field in the Interp structure. The CFWord definition is internal to here.
* Should make it easier to redo the data structures if we find something more
* space/time efficient.
*/
/*
*----------------------------------------------------------------------
*
* TclArgumentEnter --
*
* This procedure is a helper for the TIP #280 uplevel extension. It
* enters location references for the arguments of a command to be
* invoked. Only the first entry has the actual data, further entries
* simply count the usage up.
*
* Results:
* None.
*
* Side effects:
* May allocate memory.
*
* TIP #280
*----------------------------------------------------------------------
*/
void
TclArgumentEnter(
Tcl_Interp *interp,
Tcl_Obj **objv,
int objc,
CmdFrame *cfPtr)
{
Interp *iPtr = (Interp *) interp;
int new, i;
Tcl_HashEntry *hPtr;
CFWord *cfwPtr;
for (i = 1; i < objc; i++) {
/*
* Ignore argument words without line information (= dynamic). If they
* are variables they may have location information associated with
* that, either through globally recorded 'set' invokations, or
* literals in bytecode. Eitehr way there is no need to record
* something here.
*/
if (cfPtr->line[i] < 0) {
continue;
}
hPtr = Tcl_CreateHashEntry(iPtr->lineLAPtr, objv[i], &new);
if (new) {
/*
* The word is not on the stack yet, remember the current location
* and initialize references.
*/
cfwPtr = ckalloc(sizeof(CFWord));
cfwPtr->framePtr = cfPtr;
cfwPtr->word = i;
cfwPtr->refCount = 1;
Tcl_SetHashValue(hPtr, cfwPtr);
} else {
/*
* The word is already on the stack, its current location is not
* relevant. Just remember the reference to prevent early removal.
*/
cfwPtr = Tcl_GetHashValue(hPtr);
cfwPtr->refCount++;
}
}
}
/*
*----------------------------------------------------------------------
*
* TclArgumentRelease --
*
* This procedure is a helper for the TIP #280 uplevel extension. It
* removes the location references for the arguments of a command just
* done. Usage is counted down, the data is removed only when no user is
* left over.
*
* Results:
* None.
*
* Side effects:
* May release memory.
*
* TIP #280
*----------------------------------------------------------------------
*/
void
TclArgumentRelease(
Tcl_Interp *interp,
Tcl_Obj **objv,
int objc)
{
Interp *iPtr = (Interp *) interp;
int i;
for (i = 1; i < objc; i++) {
CFWord *cfwPtr;
Tcl_HashEntry *hPtr =
Tcl_FindHashEntry(iPtr->lineLAPtr, (char *) objv[i]);
if (!hPtr) {
continue;
}
cfwPtr = Tcl_GetHashValue(hPtr);
cfwPtr->refCount--;
if (cfwPtr->refCount > 0) {
continue;
}
ckfree(cfwPtr);
Tcl_DeleteHashEntry(hPtr);
}
}
/*
*----------------------------------------------------------------------
*
* TclArgumentBCEnter --
*
* This procedure is a helper for the TIP #280 uplevel extension. It
* enters location references for the literal arguments of commands in
* bytecode about to be invoked. Only the first entry has the actual
* data, further entries simply count the usage up.
*
* Results:
* None.
*
* Side effects:
* May allocate memory.
*
* TIP #280
*----------------------------------------------------------------------
*/
void
TclArgumentBCEnter(
Tcl_Interp *interp,
Tcl_Obj *objv[],
int objc,
void *codePtr,
CmdFrame *cfPtr,
int cmd,
int pc)
{
ExtCmdLoc *eclPtr;
int word;
ECL *ePtr;
CFWordBC *lastPtr = NULL;
Interp *iPtr = (Interp *) interp;
Tcl_HashEntry *hePtr =
Tcl_FindHashEntry(iPtr->lineBCPtr, (char *) codePtr);
if (!hePtr) {
return;
}
eclPtr = Tcl_GetHashValue(hePtr);
ePtr = &eclPtr->loc[cmd];
/*
* ePtr->nline is the number of words originally parsed.
*
* objc is the number of elements getting invoked.
*
* If they are not the same, we arrived here by compiling an
* ensemble dispatch. Ensemble subcommands that lead to script
* evaluation are not supposed to get compiled, because a command
* such as [info level] in the script can expose some of the dispatch
* shenanigans. This means that we don't have to tend to the
* housekeeping, and can escape now.
*/
if (ePtr->nline != objc) {
return;
}
/*
* Having disposed of the ensemble cases, we can state...
* A few truths ...
* (1) ePtr->nline == objc
* (2) (ePtr->line[word] < 0) => !literal, for all words
* (3) (word == 0) => !literal
*
* Item (2) is why we can use objv to get the literals, and do not
* have to save them at compile time.
*/
for (word = 1; word < objc; word++) {
if (ePtr->line[word] >= 0) {
int isnew;
Tcl_HashEntry *hPtr = Tcl_CreateHashEntry(iPtr->lineLABCPtr,
objv[word], &isnew);
CFWordBC *cfwPtr = ckalloc(sizeof(CFWordBC));
cfwPtr->framePtr = cfPtr;
cfwPtr->obj = objv[word];
cfwPtr->pc = pc;
cfwPtr->word = word;
cfwPtr->nextPtr = lastPtr;
lastPtr = cfwPtr;
if (isnew) {
/*
* The word is not on the stack yet, remember the current
* location and initialize references.
*/
cfwPtr->prevPtr = NULL;
} else {
/*
* The object is already on the stack, however it may have
* a different location now (literal sharing may map
* multiple location to a single Tcl_Obj*. Save the old
* information in the new structure.
*/
cfwPtr->prevPtr = Tcl_GetHashValue(hPtr);
}
Tcl_SetHashValue(hPtr, cfwPtr);
}
} /* for */
cfPtr->litarg = lastPtr;
}
/*
*----------------------------------------------------------------------
*
* TclArgumentBCRelease --
*
* This procedure is a helper for the TIP #280 uplevel extension. It
* removes the location references for the literal arguments of commands
* in bytecode just done. Usage is counted down, the data is removed only
* when no user is left over.
*
* Results:
* None.
*
* Side effects:
* May release memory.
*
* TIP #280
*----------------------------------------------------------------------
*/
void
TclArgumentBCRelease(
Tcl_Interp *interp,
CmdFrame *cfPtr)
{
Interp *iPtr = (Interp *) interp;
CFWordBC *cfwPtr = (CFWordBC *) cfPtr->litarg;
while (cfwPtr) {
CFWordBC *nextPtr = cfwPtr->nextPtr;
Tcl_HashEntry *hPtr =
Tcl_FindHashEntry(iPtr->lineLABCPtr, (char *) cfwPtr->obj);
CFWordBC *xPtr = Tcl_GetHashValue(hPtr);
if (xPtr != cfwPtr) {
Tcl_Panic("TclArgumentBC Enter/Release Mismatch");
}
if (cfwPtr->prevPtr) {
Tcl_SetHashValue(hPtr, cfwPtr->prevPtr);
} else {
Tcl_DeleteHashEntry(hPtr);
}
ckfree(cfwPtr);
cfwPtr = nextPtr;
}
cfPtr->litarg = NULL;
}
/*
*----------------------------------------------------------------------
*
* TclArgumentGet --
*
* This procedure is a helper for the TIP #280 uplevel extension. It
* finds the location references for a Tcl_Obj, if any.
*
* Results:
* None.
*
* Side effects:
* Writes found location information into the result arguments.
*
* TIP #280
*----------------------------------------------------------------------
*/
void
TclArgumentGet(
Tcl_Interp *interp,
Tcl_Obj *obj,
CmdFrame **cfPtrPtr,
int *wordPtr)
{
Interp *iPtr = (Interp *) interp;
Tcl_HashEntry *hPtr;
CmdFrame *framePtr;
/*
* An object which either has no string rep or else is a canonical list is
* guaranteed to have been generated dynamically: bail out, this cannot
* have a usable absolute location. _Do not touch_ the information the set
* up by the caller. It knows better than us.
*/
if ((obj->bytes == NULL) || TclListObjIsCanonical(obj)) {
return;
}
/*
* First look for location information recorded in the argument
* stack. That is nearest.
*/
hPtr = Tcl_FindHashEntry(iPtr->lineLAPtr, (char *) obj);
if (hPtr) {
CFWord *cfwPtr = Tcl_GetHashValue(hPtr);
*wordPtr = cfwPtr->word;
*cfPtrPtr = cfwPtr->framePtr;
return;
}
/*
* Check if the Tcl_Obj has location information as a bytecode literal, in
* that stack.
*/
hPtr = Tcl_FindHashEntry(iPtr->lineLABCPtr, (char *) obj);
if (hPtr) {
CFWordBC *cfwPtr = Tcl_GetHashValue(hPtr);
framePtr = cfwPtr->framePtr;
framePtr->data.tebc.pc = (char *) (((ByteCode *)
framePtr->data.tebc.codePtr)->codeStart + cfwPtr->pc);
*cfPtrPtr = cfwPtr->framePtr;
*wordPtr = cfwPtr->word;
return;
}
}
/*
*----------------------------------------------------------------------
*
* Tcl_Eval --
*
* Execute a Tcl command in a string. This function executes the script
* directly, rather than compiling it to bytecodes. Before the arrival of
* the bytecode compiler in Tcl 8.0 Tcl_Eval was the main function used
* for executing Tcl commands, but nowadays it isn't used much.
*
* Results:
* The return value is one of the return codes defined in tcl.h (such as
* TCL_OK), and interp's result contains a value to supplement the return
* code. The value of the result will persist only until the next call to
* Tcl_Eval or Tcl_EvalObj: you must copy it or lose it!
*
* Side effects:
* Can be almost arbitrary, depending on the commands in the script.
*
*----------------------------------------------------------------------
*/
#undef Tcl_Eval
int
Tcl_Eval(
Tcl_Interp *interp, /* Token for command interpreter (returned by
* previous call to Tcl_CreateInterp). */
const char *script) /* Pointer to TCL command to execute. */
{
int code = Tcl_EvalEx(interp, script, -1, 0);
/*
* For backwards compatibility with old C code that predates the object
* system in Tcl 8.0, we have to mirror the object result back into the
* string result (some callers may expect it there).
*/
(void) Tcl_GetStringResult(interp);
return code;
}
/*
*----------------------------------------------------------------------
*
* Tcl_EvalObj, Tcl_GlobalEvalObj --
*
* These functions are deprecated but we keep them around for backwards
* compatibility reasons.
*
* Results:
* See the functions they call.
*
* Side effects:
* See the functions they call.
*
*----------------------------------------------------------------------
*/
#undef Tcl_EvalObj
int
Tcl_EvalObj(
Tcl_Interp *interp,
Tcl_Obj *objPtr)
{
return Tcl_EvalObjEx(interp, objPtr, 0);
}
#undef Tcl_GlobalEvalObj
int
Tcl_GlobalEvalObj(
Tcl_Interp *interp,
Tcl_Obj *objPtr)
{
return Tcl_EvalObjEx(interp, objPtr, TCL_EVAL_GLOBAL);
}
/*
*----------------------------------------------------------------------
*
* Tcl_EvalObjEx, TclEvalObjEx --
*
* Execute Tcl commands stored in a Tcl object. These commands are
* compiled into bytecodes if necessary, unless TCL_EVAL_DIRECT is
* specified.
*
* If the flag TCL_EVAL_DIRECT is passed in, the value of invoker
* must be NULL. Support for non-NULL invokers in that mode has
* been removed since it was unused and untested. Failure to
* follow this limitation will lead to an assertion panic.
*
* Results:
* The return value is one of the return codes defined in tcl.h (such as
* TCL_OK), and the interpreter's result contains a value to supplement
* the return code.
*
* Side effects:
* The object is converted, if necessary, to a ByteCode object that holds
* the bytecode instructions for the commands. Executing the commands
* will almost certainly have side effects that depend on those commands.
*
* TIP #280 : Keep public API, internally extended API.
*----------------------------------------------------------------------
*/
int
Tcl_EvalObjEx(
Tcl_Interp *interp, /* Token for command interpreter (returned by
* a previous call to Tcl_CreateInterp). */
Tcl_Obj *objPtr, /* Pointer to object containing commands to
* execute. */
int flags) /* Collection of OR-ed bits that control the
* evaluation of the script. Supported values
* are TCL_EVAL_GLOBAL and TCL_EVAL_DIRECT. */
{
return TclEvalObjEx(interp, objPtr, flags, NULL, 0);
}
int
TclEvalObjEx(
Tcl_Interp *interp, /* Token for command interpreter (returned by
* a previous call to Tcl_CreateInterp). */
Tcl_Obj *objPtr, /* Pointer to object containing commands to
* execute. */
int flags, /* Collection of OR-ed bits that control the
* evaluation of the script. Supported values
* are TCL_EVAL_GLOBAL and TCL_EVAL_DIRECT. */
const CmdFrame *invoker, /* Frame of the command doing the eval. */
int word) /* Index of the word which is in objPtr. */
{
int result = TCL_OK;
NRE_callback *rootPtr = TOP_CB(interp);
result = TclNREvalObjEx(interp, objPtr, flags, invoker, word);
return TclNRRunCallbacks(interp, result, rootPtr);
}
int
TclNREvalObjEx(
Tcl_Interp *interp, /* Token for command interpreter (returned by
* a previous call to Tcl_CreateInterp). */
Tcl_Obj *objPtr, /* Pointer to object containing commands to
* execute. */
int flags, /* Collection of OR-ed bits that control the
* evaluation of the script. Supported values
* are TCL_EVAL_GLOBAL and TCL_EVAL_DIRECT. */
const CmdFrame *invoker, /* Frame of the command doing the eval. */
int word) /* Index of the word which is in objPtr. */
{
Interp *iPtr = (Interp *) interp;
int result;
/*
* This function consists of three independent blocks for: direct
* evaluation of canonical lists, compilation and bytecode execution and
* finally direct evaluation. Precisely one of these blocks will be run.
*/
if (TclListObjIsCanonical(objPtr)) {
CmdFrame *eoFramePtr = NULL;
int objc;
Tcl_Obj *listPtr, **objv;
/*
* Canonical List Optimization: In this case, we
* can safely use Tcl_EvalObjv instead and get an appreciable
* improvement in execution speed. This is because it allows us to
* avoid a setFromAny step that would just pack everything into a
* string and back out again.
*
* This also preserves any associations between list elements and
* location information for such elements.
*/
/*
* Shimmer protection! Always pass an unshared obj. The caller could
* incr the refCount of objPtr AFTER calling us! To be completely safe
* we always make a copy. The callback takes care od the refCounts for
* both listPtr and objPtr.
*
* TODO: Create a test to demo this need, or eliminate it.
* FIXME OPT: preserve just the internal rep?
*/
Tcl_IncrRefCount(objPtr);
listPtr = TclListObjCopy(interp, objPtr);
Tcl_IncrRefCount(listPtr);
if (word != INT_MIN) {
/*
* TIP #280 Structures for tracking lines. As we know that this is
* dynamic execution we ignore the invoker, even if known.
*
* TIP #280. We do _not_ compute all the line numbers for the
* words in the command. For the eval of a pure list the most
* sensible choice is to put all words on line 1. Given that we
* neither need memory for them nor compute anything. 'line' is
* left NULL. The two places using this information (TclInfoFrame,
* and TclInitCompileEnv), are special-cased to use the proper
* line number directly instead of accessing the 'line' array.
*
* Note that we use (word==INTMIN) to signal that no command frame
* should be pushed, as needed by alias and ensemble redirections.
*/
eoFramePtr = TclStackAlloc(interp, sizeof(CmdFrame));
eoFramePtr->nline = 0;
eoFramePtr->line = NULL;
eoFramePtr->type = TCL_LOCATION_EVAL;
eoFramePtr->level = (iPtr->cmdFramePtr == NULL?
1 : iPtr->cmdFramePtr->level + 1);
eoFramePtr->framePtr = iPtr->framePtr;
eoFramePtr->nextPtr = iPtr->cmdFramePtr;
eoFramePtr->cmdObj = objPtr;
eoFramePtr->cmd = NULL;
eoFramePtr->len = 0;
eoFramePtr->data.eval.path = NULL;
iPtr->cmdFramePtr = eoFramePtr;
flags |= TCL_EVAL_SOURCE_IN_FRAME;
}
TclMarkTailcall(interp);
TclNRAddCallback(interp, TEOEx_ListCallback, listPtr, eoFramePtr,
objPtr, NULL);
ListObjGetElements(listPtr, objc, objv);
return TclNREvalObjv(interp, objc, objv, flags, NULL);
}
if (!(flags & TCL_EVAL_DIRECT)) {
/*
* Let the compiler/engine subsystem do the evaluation.
*
* TIP #280 The invoker provides us with the context for the script.
* We transfer this to the byte code compiler.
*/
int allowExceptions = (iPtr->evalFlags & TCL_ALLOW_EXCEPTIONS);
ByteCode *codePtr;
CallFrame *savedVarFramePtr = NULL; /* Saves old copy of
* iPtr->varFramePtr in case
* TCL_EVAL_GLOBAL was set. */
if (TclInterpReady(interp) != TCL_OK) {
return TCL_ERROR;
}
if (flags & TCL_EVAL_GLOBAL) {
savedVarFramePtr = iPtr->varFramePtr;
iPtr->varFramePtr = iPtr->rootFramePtr;
}
Tcl_IncrRefCount(objPtr);
codePtr = TclCompileObj(interp, objPtr, invoker, word);
TclNRAddCallback(interp, TEOEx_ByteCodeCallback, savedVarFramePtr,
objPtr, INT2PTR(allowExceptions), NULL);
return TclNRExecuteByteCode(interp, codePtr);
}
{
/*
* We're not supposed to use the compiler or byte-code
* interpreter. Let Tcl_EvalEx evaluate the command directly (and
* probably more slowly).
*/
const char *script;
int numSrcBytes;
/*
* Now we check if we have data about invisible continuation lines for
* the script, and make it available to the direct script parser and
* evaluator we are about to call, if so.
*
* It may be possible that the script Tcl_Obj* can be free'd while the
* evaluator is using it, leading to the release of the associated
* ContLineLoc structure as well. To ensure that the latter doesn't
* happen we set a lock on it. We release this lock later in this
* function, after the evaluator is done. The relevant "lineCLPtr"
* hashtable is managed in the file "tclObj.c".
*
* Another important action is to save (and later restore) the
* continuation line information of the caller, in case we are
* executing nested commands in the eval/direct path.
*/
ContLineLoc *saveCLLocPtr = iPtr->scriptCLLocPtr;
assert(invoker == NULL);
iPtr->scriptCLLocPtr = TclContinuationsGet(objPtr);
Tcl_IncrRefCount(objPtr);
script = Tcl_GetStringFromObj(objPtr, &numSrcBytes);
result = Tcl_EvalEx(interp, script, numSrcBytes, flags);
TclDecrRefCount(objPtr);
iPtr->scriptCLLocPtr = saveCLLocPtr;
return result;
}
}
static int
TEOEx_ByteCodeCallback(
ClientData data[],
Tcl_Interp *interp,
int result)
{
Interp *iPtr = (Interp *) interp;
CallFrame *savedVarFramePtr = data[0];
Tcl_Obj *objPtr = data[1];
int allowExceptions = PTR2INT(data[2]);
if (iPtr->numLevels == 0) {
if (result == TCL_RETURN) {
result = TclUpdateReturnInfo(iPtr);
}
if ((result != TCL_OK) && (result != TCL_ERROR) && !allowExceptions) {
const char *script;
int numSrcBytes;
ProcessUnexpectedResult(interp, result);
result = TCL_ERROR;
script = Tcl_GetStringFromObj(objPtr, &numSrcBytes);
Tcl_LogCommandInfo(interp, script, script, numSrcBytes);
}
/*
* We are returning to level 0, so should call TclResetCancellation.
* Let us just unset the flags inline.
*/
TclUnsetCancelFlags(iPtr);
}
iPtr->evalFlags = 0;
/*
* Restore the callFrame if this was a TCL_EVAL_GLOBAL.
*/
if (savedVarFramePtr) {
iPtr->varFramePtr = savedVarFramePtr;
}
TclDecrRefCount(objPtr);
return result;
}
static int
TEOEx_ListCallback(
ClientData data[],
Tcl_Interp *interp,
int result)
{
Interp *iPtr = (Interp *) interp;
Tcl_Obj *listPtr = data[0];
CmdFrame *eoFramePtr = data[1];
Tcl_Obj *objPtr = data[2];
/*
* Remove the cmdFrame
*/
if (eoFramePtr) {
iPtr->cmdFramePtr = eoFramePtr->nextPtr;
TclStackFree(interp, eoFramePtr);
}
TclDecrRefCount(objPtr);
TclDecrRefCount(listPtr);
return result;
}
/*
*----------------------------------------------------------------------
*
* ProcessUnexpectedResult --
*
* Function called by Tcl_EvalObj to set the interpreter's result value
* to an appropriate error message when the code it evaluates returns an
* unexpected result code (not TCL_OK and not TCL_ERROR) to the topmost
* evaluation level.
*
* Results:
* None.
*
* Side effects:
* The interpreter result is set to an error message appropriate to the
* result code.
*
*----------------------------------------------------------------------
*/
static void
ProcessUnexpectedResult(
Tcl_Interp *interp, /* The interpreter in which the unexpected
* result code was returned. */
int returnCode) /* The unexpected result code. */
{
char buf[TCL_INTEGER_SPACE];
Tcl_ResetResult(interp);
if (returnCode == TCL_BREAK) {
Tcl_SetObjResult(interp, Tcl_NewStringObj(
"invoked \"break\" outside of a loop", -1));
} else if (returnCode == TCL_CONTINUE) {
Tcl_SetObjResult(interp, Tcl_NewStringObj(
"invoked \"continue\" outside of a loop", -1));
} else {
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
"command returned bad code: %d", returnCode));
}
sprintf(buf, "%d", returnCode);
Tcl_SetErrorCode(interp, "TCL", "UNEXPECTED_RESULT_CODE", buf, NULL);
}
/*
*---------------------------------------------------------------------------
*
* Tcl_ExprLong, Tcl_ExprDouble, Tcl_ExprBoolean --
*
* Functions to evaluate an expression and return its value in a
* particular form.
*
* Results:
* Each of the functions below returns a standard Tcl result. If an error
* occurs then an error message is left in the interp's result. Otherwise
* the value of the expression, in the appropriate form, is stored at
* *ptr. If the expression had a result that was incompatible with the
* desired form then an error is returned.
*
* Side effects:
* None.
*
*---------------------------------------------------------------------------
*/
int
Tcl_ExprLong(
Tcl_Interp *interp, /* Context in which to evaluate the
* expression. */
const char *exprstring, /* Expression to evaluate. */
long *ptr) /* Where to store result. */
{
Tcl_Obj *exprPtr;
int result = TCL_OK;
if (*exprstring == '\0') {
/*
* Legacy compatibility - return 0 for the zero-length string.
*/
*ptr = 0;
} else {
exprPtr = Tcl_NewStringObj(exprstring, -1);
Tcl_IncrRefCount(exprPtr);
result = Tcl_ExprLongObj(interp, exprPtr, ptr);
Tcl_DecrRefCount(exprPtr);
if (result != TCL_OK) {
(void) Tcl_GetStringResult(interp);
}
}
return result;
}
int
Tcl_ExprDouble(
Tcl_Interp *interp, /* Context in which to evaluate the
* expression. */
const char *exprstring, /* Expression to evaluate. */
double *ptr) /* Where to store result. */
{
Tcl_Obj *exprPtr;
int result = TCL_OK;
if (*exprstring == '\0') {
/*
* Legacy compatibility - return 0 for the zero-length string.
*/
*ptr = 0.0;
} else {
exprPtr = Tcl_NewStringObj(exprstring, -1);
Tcl_IncrRefCount(exprPtr);
result = Tcl_ExprDoubleObj(interp, exprPtr, ptr);
Tcl_DecrRefCount(exprPtr);
/* Discard the expression object. */
if (result != TCL_OK) {
(void) Tcl_GetStringResult(interp);
}
}
return result;
}
int
Tcl_ExprBoolean(
Tcl_Interp *interp, /* Context in which to evaluate the
* expression. */
const char *exprstring, /* Expression to evaluate. */
int *ptr) /* Where to store 0/1 result. */
{
if (*exprstring == '\0') {
/*
* An empty string. Just set the result boolean to 0 (false).
*/
*ptr = 0;
return TCL_OK;
} else {
int result;
Tcl_Obj *exprPtr = Tcl_NewStringObj(exprstring, -1);
Tcl_IncrRefCount(exprPtr);
result = Tcl_ExprBooleanObj(interp, exprPtr, ptr);
Tcl_DecrRefCount(exprPtr);
if (result != TCL_OK) {
/*
* Move the interpreter's object result to the string result, then
* reset the object result.
*/
(void) Tcl_GetStringResult(interp);
}
return result;
}
}
/*
*--------------------------------------------------------------
*
* Tcl_ExprLongObj, Tcl_ExprDoubleObj, Tcl_ExprBooleanObj --
*
* Functions to evaluate an expression in an object and return its value
* in a particular form.
*
* Results:
* Each of the functions below returns a standard Tcl result object. If
* an error occurs then an error message is left in the interpreter's
* result. Otherwise the value of the expression, in the appropriate
* form, is stored at *ptr. If the expression had a result that was
* incompatible with the desired form then an error is returned.
*
* Side effects:
* None.
*
*--------------------------------------------------------------
*/
int
Tcl_ExprLongObj(
Tcl_Interp *interp, /* Context in which to evaluate the
* expression. */
Tcl_Obj *objPtr, /* Expression to evaluate. */
long *ptr) /* Where to store long result. */
{
Tcl_Obj *resultPtr;
int result, type;
double d;
ClientData internalPtr;
result = Tcl_ExprObj(interp, objPtr, &resultPtr);
if (result != TCL_OK) {
return TCL_ERROR;
}
if (TclGetNumberFromObj(interp, resultPtr, &internalPtr, &type)!=TCL_OK) {
return TCL_ERROR;
}
switch (type) {
case TCL_NUMBER_DOUBLE: {
mp_int big;
d = *((const double *) internalPtr);
Tcl_DecrRefCount(resultPtr);
if (Tcl_InitBignumFromDouble(interp, d, &big) != TCL_OK) {
return TCL_ERROR;
}
resultPtr = Tcl_NewBignumObj(&big);
}
/* FALLTHRU */
case TCL_NUMBER_LONG:
case TCL_NUMBER_WIDE:
case TCL_NUMBER_BIG:
result = TclGetLongFromObj(interp, resultPtr, ptr);
break;
case TCL_NUMBER_NAN:
Tcl_GetDoubleFromObj(interp, resultPtr, &d);
result = TCL_ERROR;
}
Tcl_DecrRefCount(resultPtr);/* Discard the result object. */
return result;
}
int
Tcl_ExprDoubleObj(
Tcl_Interp *interp, /* Context in which to evaluate the
* expression. */
Tcl_Obj *objPtr, /* Expression to evaluate. */
double *ptr) /* Where to store double result. */
{
Tcl_Obj *resultPtr;
int result, type;
ClientData internalPtr;
result = Tcl_ExprObj(interp, objPtr, &resultPtr);
if (result != TCL_OK) {
return TCL_ERROR;
}
result = TclGetNumberFromObj(interp, resultPtr, &internalPtr, &type);
if (result == TCL_OK) {
switch (type) {
case TCL_NUMBER_NAN:
#ifndef ACCEPT_NAN
result = Tcl_GetDoubleFromObj(interp, resultPtr, ptr);
break;
#endif
case TCL_NUMBER_DOUBLE:
*ptr = *((const double *) internalPtr);
result = TCL_OK;
break;
default:
result = Tcl_GetDoubleFromObj(interp, resultPtr, ptr);
}
}
Tcl_DecrRefCount(resultPtr);/* Discard the result object. */
return result;
}
int
Tcl_ExprBooleanObj(
Tcl_Interp *interp, /* Context in which to evaluate the
* expression. */
Tcl_Obj *objPtr, /* Expression to evaluate. */
int *ptr) /* Where to store 0/1 result. */
{
Tcl_Obj *resultPtr;
int result;
result = Tcl_ExprObj(interp, objPtr, &resultPtr);
if (result == TCL_OK) {
result = Tcl_GetBooleanFromObj(interp, resultPtr, ptr);
Tcl_DecrRefCount(resultPtr);
/* Discard the result object. */
}
return result;
}
/*
*----------------------------------------------------------------------
*
* TclObjInvokeNamespace --
*
* Object version: Invokes a Tcl command, given an objv/objc, from either
* the exposed or hidden set of commands in the given interpreter.
*
* NOTE: The command is invoked in the global stack frame of the
* interpreter or namespace, thus it cannot see any current state on the
* stack of that interpreter.
*
* Results:
* A standard Tcl result.
*
* Side effects:
* Whatever the command does.
*
*----------------------------------------------------------------------
*/
int
TclObjInvokeNamespace(
Tcl_Interp *interp, /* Interpreter in which command is to be
* invoked. */
int objc, /* Count of arguments. */
Tcl_Obj *const objv[], /* Argument objects; objv[0] points to the
* name of the command to invoke. */
Tcl_Namespace *nsPtr, /* The namespace to use. */
int flags) /* Combination of flags controlling the call:
* TCL_INVOKE_HIDDEN, TCL_INVOKE_NO_UNKNOWN,
* or TCL_INVOKE_NO_TRACEBACK. */
{
int result;
Tcl_CallFrame *framePtr;
/*
* Make the specified namespace the current namespace and invoke the
* command.
*/
(void) TclPushStackFrame(interp, &framePtr, nsPtr, /*isProcFrame*/0);
result = TclObjInvoke(interp, objc, objv, flags);
TclPopStackFrame(interp);
return result;
}
/*
*----------------------------------------------------------------------
*
* TclObjInvoke --
*
* Invokes a Tcl command, given an objv/objc, from either the exposed or
* the hidden sets of commands in the given interpreter.
*
* Results:
* A standard Tcl object result.
*
* Side effects:
* Whatever the command does.
*
*----------------------------------------------------------------------
*/
int
TclObjInvoke(
Tcl_Interp *interp, /* Interpreter in which command is to be
* invoked. */
int objc, /* Count of arguments. */
Tcl_Obj *const objv[], /* Argument objects; objv[0] points to the
* name of the command to invoke. */
int flags) /* Combination of flags controlling the call:
* TCL_INVOKE_HIDDEN, TCL_INVOKE_NO_UNKNOWN,
* or TCL_INVOKE_NO_TRACEBACK. */
{
if (interp == NULL) {
return TCL_ERROR;
}
if ((objc < 1) || (objv == NULL)) {
Tcl_SetObjResult(interp, Tcl_NewStringObj(
"illegal argument vector", -1));
return TCL_ERROR;
}
if ((flags & TCL_INVOKE_HIDDEN) == 0) {
Tcl_Panic("TclObjInvoke: called without TCL_INVOKE_HIDDEN");
}
return Tcl_NRCallObjProc(interp, TclNRInvoke, NULL, objc, objv);
}
int
TclNRInvoke(
ClientData clientData,
Tcl_Interp *interp,
int objc,
Tcl_Obj *const objv[])
{
Interp *iPtr = (Interp *) interp;
Tcl_HashTable *hTblPtr; /* Table of hidden commands. */
const char *cmdName; /* Name of the command from objv[0]. */
Tcl_HashEntry *hPtr = NULL;
Command *cmdPtr;
cmdName = TclGetString(objv[0]);
hTblPtr = iPtr->hiddenCmdTablePtr;
if (hTblPtr != NULL) {
hPtr = Tcl_FindHashEntry(hTblPtr, cmdName);
}
if (hPtr == NULL) {
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
"invalid hidden command name \"%s\"", cmdName));
Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "HIDDENTOKEN", cmdName,
NULL);
return TCL_ERROR;
}
cmdPtr = Tcl_GetHashValue(hPtr);
/*
* Avoid the exception-handling brain damage when numLevels == 0
*/
iPtr->numLevels++;
Tcl_NRAddCallback(interp, NRPostInvoke, NULL, NULL, NULL, NULL);
/*
* Normal command resolution of objv[0] isn't going to find cmdPtr.
* That's the whole point of **hidden** commands. So tell the Eval core
* machinery not to even try (and risk finding something wrong).
*/
return TclNREvalObjv(interp, objc, objv, TCL_EVAL_NORESOLVE, cmdPtr);
}
static int
NRPostInvoke(
ClientData clientData[],
Tcl_Interp *interp,
int result)
{
Interp *iPtr = (Interp *)interp;
iPtr->numLevels--;
return result;
}
/*
*---------------------------------------------------------------------------
*
* Tcl_ExprString --
*
* Evaluate an expression in a string and return its value in string
* form.
*
* Results:
* A standard Tcl result. If the result is TCL_OK, then the interp's
* result is set to the string value of the expression. If the result is
* TCL_ERROR, then the interp's result contains an error message.
*
* Side effects:
* A Tcl object is allocated to hold a copy of the expression string.
* This expression object is passed to Tcl_ExprObj and then deallocated.
*
*---------------------------------------------------------------------------
*/
int
Tcl_ExprString(
Tcl_Interp *interp, /* Context in which to evaluate the
* expression. */
const char *expr) /* Expression to evaluate. */
{
int code = TCL_OK;
if (expr[0] == '\0') {
/*
* An empty string. Just set the interpreter's result to 0.
*/
Tcl_SetObjResult(interp, Tcl_NewIntObj(0));
} else {
Tcl_Obj *resultPtr, *exprObj = Tcl_NewStringObj(expr, -1);
Tcl_IncrRefCount(exprObj);
code = Tcl_ExprObj(interp, exprObj, &resultPtr);
Tcl_DecrRefCount(exprObj);
if (code == TCL_OK) {
Tcl_SetObjResult(interp, resultPtr);
Tcl_DecrRefCount(resultPtr);
}
}
/*
* Force the string rep of the interp result.
*/
(void) Tcl_GetStringResult(interp);
return code;
}
/*
*----------------------------------------------------------------------
*
* Tcl_AppendObjToErrorInfo --
*
* Add a Tcl_Obj value to the errorInfo field that describes the current
* error.
*
* Results:
* None.
*
* Side effects:
* The value of the Tcl_obj is appended to the errorInfo field. If we are
* just starting to log an error, errorInfo is initialized from the error
* message in the interpreter's result.
*
*----------------------------------------------------------------------
*/
#undef Tcl_AddObjErrorInfo
void
Tcl_AppendObjToErrorInfo(
Tcl_Interp *interp, /* Interpreter to which error information
* pertains. */
Tcl_Obj *objPtr) /* Message to record. */
{
int length;
const char *message = TclGetStringFromObj(objPtr, &length);
Tcl_IncrRefCount(objPtr);
Tcl_AddObjErrorInfo(interp, message, length);
Tcl_DecrRefCount(objPtr);
}
/*
*----------------------------------------------------------------------
*
* Tcl_AddErrorInfo --
*
* Add information to the errorInfo field that describes the current
* error.
*
* Results:
* None.
*
* Side effects:
* The contents of message are appended to the errorInfo field. If we are
* just starting to log an error, errorInfo is initialized from the error
* message in the interpreter's result.
*
*----------------------------------------------------------------------
*/
#undef Tcl_AddErrorInfo
void
Tcl_AddErrorInfo(
Tcl_Interp *interp, /* Interpreter to which error information
* pertains. */
const char *message) /* Message to record. */
{
Tcl_AddObjErrorInfo(interp, message, -1);
}
/*
*----------------------------------------------------------------------
*
* Tcl_AddObjErrorInfo --
*
* Add information to the errorInfo field that describes the current
* error. This routine differs from Tcl_AddErrorInfo by taking a byte
* pointer and length.
*
* Results:
* None.
*
* Side effects:
* "length" bytes from "message" are appended to the errorInfo field. If
* "length" is negative, use bytes up to the first NULL byte. If we are
* just starting to log an error, errorInfo is initialized from the error
* message in the interpreter's result.
*
*----------------------------------------------------------------------
*/
void
Tcl_AddObjErrorInfo(
Tcl_Interp *interp, /* Interpreter to which error information
* pertains. */
const char *message, /* Points to the first byte of an array of
* bytes of the message. */
int length) /* The number of bytes in the message. If < 0,
* then append all bytes up to a NULL byte. */
{
Interp *iPtr = (Interp *) interp;
/*
* If we are just starting to log an error, errorInfo is initialized from
* the error message in the interpreter's result.
*/
iPtr->flags |= ERR_LEGACY_COPY;
if (iPtr->errorInfo == NULL) {
if (iPtr->result[0] != 0) {
/*
* The interp's string result is set, apparently by some extension
* making a deprecated direct write to it. That extension may
* expect interp->result to continue to be set, so we'll take
* special pains to avoid clearing it, until we drop support for
* interp->result completely.
*/
iPtr->errorInfo = Tcl_NewStringObj(iPtr->result, -1);
} else {
iPtr->errorInfo = iPtr->objResultPtr;
}
Tcl_IncrRefCount(iPtr->errorInfo);
if (!iPtr->errorCode) {
Tcl_SetErrorCode(interp, "NONE", NULL);
}
}
/*
* Now append "message" to the end of errorInfo.
*/
if (length != 0) {
if (Tcl_IsShared(iPtr->errorInfo)) {
Tcl_DecrRefCount(iPtr->errorInfo);
iPtr->errorInfo = Tcl_DuplicateObj(iPtr->errorInfo);
Tcl_IncrRefCount(iPtr->errorInfo);
}
Tcl_AppendToObj(iPtr->errorInfo, message, length);
}
}
/*
*---------------------------------------------------------------------------
*
* Tcl_VarEvalVA --
*
* Given a variable number of string arguments, concatenate them all
* together and execute the result as a Tcl command.
*
* Results:
* A standard Tcl return result. An error message or other result may be
* left in the interp's result.
*
* Side effects:
* Depends on what was done by the command.
*
*---------------------------------------------------------------------------
*/
int
Tcl_VarEvalVA(
Tcl_Interp *interp, /* Interpreter in which to evaluate command */
va_list argList) /* Variable argument list. */
{
Tcl_DString buf;
char *string;
int result;
/*
* Copy the strings one after the other into a single larger string. Use
* stack-allocated space for small commands, but if the command gets too
* large than call ckalloc to create the space.
*/
Tcl_DStringInit(&buf);
while (1) {
string = va_arg(argList, char *);
if (string == NULL) {
break;
}
Tcl_DStringAppend(&buf, string, -1);
}
result = Tcl_Eval(interp, Tcl_DStringValue(&buf));
Tcl_DStringFree(&buf);
return result;
}
/*
*----------------------------------------------------------------------
*
* Tcl_VarEval --
*
* Given a variable number of string arguments, concatenate them all
* together and execute the result as a Tcl command.
*
* Results:
* A standard Tcl return result. An error message or other result may be
* left in interp->result.
*
* Side effects:
* Depends on what was done by the command.
*
*----------------------------------------------------------------------
*/
/* ARGSUSED */
int
Tcl_VarEval(
Tcl_Interp *interp,
...)
{
va_list argList;
int result;
va_start(argList, interp);
result = Tcl_VarEvalVA(interp, argList);
va_end(argList);
return result;
}
/*
*----------------------------------------------------------------------
*
* Tcl_GlobalEval --
*
* Evaluate a command at global level in an interpreter.
*
* Results:
* A standard Tcl result is returned, and the interp's result is modified
* accordingly.
*
* Side effects:
* The command string is executed in interp, and the execution is carried
* out in the variable context of global level (no functions active),
* just as if an "uplevel #0" command were being executed.
*
*----------------------------------------------------------------------
*/
#undef Tcl_GlobalEval
int
Tcl_GlobalEval(
Tcl_Interp *interp, /* Interpreter in which to evaluate
* command. */
const char *command) /* Command to evaluate. */
{
Interp *iPtr = (Interp *) interp;
int result;
CallFrame *savedVarFramePtr;
savedVarFramePtr = iPtr->varFramePtr;
iPtr->varFramePtr = iPtr->rootFramePtr;
result = Tcl_Eval(interp, command);
iPtr->varFramePtr = savedVarFramePtr;
return result;
}
/*
*----------------------------------------------------------------------
*
* Tcl_SetRecursionLimit --
*
* Set the maximum number of recursive calls that may be active for an
* interpreter at once.
*
* Results:
* The return value is the old limit on nesting for interp.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
int
Tcl_SetRecursionLimit(
Tcl_Interp *interp, /* Interpreter whose nesting limit is to be
* set. */
int depth) /* New value for maximimum depth. */
{
Interp *iPtr = (Interp *) interp;
int old;
old = iPtr->maxNestingDepth;
if (depth > 0) {
iPtr->maxNestingDepth = depth;
}
return old;
}
/*
*----------------------------------------------------------------------
*
* Tcl_AllowExceptions --
*
* Sets a flag in an interpreter so that exceptions can occur in the next
* call to Tcl_Eval without them being turned into errors.
*
* Results:
* None.
*
* Side effects:
* The TCL_ALLOW_EXCEPTIONS flag gets set in the interpreter's evalFlags
* structure. See the reference documentation for more details.
*
*----------------------------------------------------------------------
*/
void
Tcl_AllowExceptions(
Tcl_Interp *interp) /* Interpreter in which to set flag. */
{
Interp *iPtr = (Interp *) interp;
iPtr->evalFlags |= TCL_ALLOW_EXCEPTIONS;
}
/*
*----------------------------------------------------------------------
*
* Tcl_GetVersion --
*
* Get the Tcl major, minor, and patchlevel version numbers and the
* release type. A patch is a release type TCL_FINAL_RELEASE with a
* patchLevel > 0.
*
* Results:
* None.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
void
Tcl_GetVersion(
int *majorV,
int *minorV,
int *patchLevelV,
int *type)
{
if (majorV != NULL) {
*majorV = TCL_MAJOR_VERSION;
}
if (minorV != NULL) {
*minorV = TCL_MINOR_VERSION;
}
if (patchLevelV != NULL) {
*patchLevelV = TCL_RELEASE_SERIAL;
}
if (type != NULL) {
*type = TCL_RELEASE_LEVEL;
}
}
/*
*----------------------------------------------------------------------
*
* Math Functions --
*
* This page contains the functions that implement all of the built-in
* math functions for expressions.
*
* Results:
* Each function returns TCL_OK if it succeeds and pushes an Tcl object
* holding the result. If it fails it returns TCL_ERROR and leaves an
* error message in the interpreter's result.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
static int
ExprCeilFunc(
ClientData clientData, /* Ignored */
Tcl_Interp *interp, /* The interpreter in which to execute the
* function. */
int objc, /* Actual parameter count. */
Tcl_Obj *const *objv) /* Actual parameter list. */
{
int code;
double d;
mp_int big;
if (objc != 2) {
MathFuncWrongNumArgs(interp, 2, objc, objv);
return TCL_ERROR;
}
code = Tcl_GetDoubleFromObj(interp, objv[1], &d);
#ifdef ACCEPT_NAN
if ((code != TCL_OK) && (objv[1]->typePtr == &tclDoubleType)) {
Tcl_SetObjResult(interp, objv[1]);
return TCL_OK;
}
#endif
if (code != TCL_OK) {
return TCL_ERROR;
}
if (Tcl_GetBignumFromObj(NULL, objv[1], &big) == TCL_OK) {
Tcl_SetObjResult(interp, Tcl_NewDoubleObj(TclCeil(&big)));
mp_clear(&big);
} else {
Tcl_SetObjResult(interp, Tcl_NewDoubleObj(ceil(d)));
}
return TCL_OK;
}
static int
ExprFloorFunc(
ClientData clientData, /* Ignored */
Tcl_Interp *interp, /* The interpreter in which to execute the
* function. */
int objc, /* Actual parameter count. */
Tcl_Obj *const *objv) /* Actual parameter list. */
{
int code;
double d;
mp_int big;
if (objc != 2) {
MathFuncWrongNumArgs(interp, 2, objc, objv);
return TCL_ERROR;
}
code = Tcl_GetDoubleFromObj(interp, objv[1], &d);
#ifdef ACCEPT_NAN
if ((code != TCL_OK) && (objv[1]->typePtr == &tclDoubleType)) {
Tcl_SetObjResult(interp, objv[1]);
return TCL_OK;
}
#endif
if (code != TCL_OK) {
return TCL_ERROR;
}
if (Tcl_GetBignumFromObj(NULL, objv[1], &big) == TCL_OK) {
Tcl_SetObjResult(interp, Tcl_NewDoubleObj(TclFloor(&big)));
mp_clear(&big);
} else {
Tcl_SetObjResult(interp, Tcl_NewDoubleObj(floor(d)));
}
return TCL_OK;
}
static int
ExprIsqrtFunc(
ClientData clientData, /* Ignored */
Tcl_Interp *interp, /* The interpreter in which to execute. */
int objc, /* Actual parameter count. */
Tcl_Obj *const *objv) /* Actual parameter list. */
{
ClientData ptr;
int type;
double d;
Tcl_WideInt w;
mp_int big;
int exact = 0; /* Flag ==1 if the argument can be represented
* in a double as an exact integer. */
/*
* Check syntax.
*/
if (objc != 2) {
MathFuncWrongNumArgs(interp, 2, objc, objv);
return TCL_ERROR;
}
/*
* Make sure that the arg is a number.
*/
if (TclGetNumberFromObj(interp, objv[1], &ptr, &type) != TCL_OK) {
return TCL_ERROR;
}
switch (type) {
case TCL_NUMBER_NAN:
Tcl_GetDoubleFromObj(interp, objv[1], &d);
return TCL_ERROR;
case TCL_NUMBER_DOUBLE:
d = *((const double *) ptr);
if (d < 0) {
goto negarg;
}
#ifdef IEEE_FLOATING_POINT
if (d <= MAX_EXACT) {
exact = 1;
}
#endif
if (!exact) {
if (Tcl_InitBignumFromDouble(interp, d, &big) != TCL_OK) {
return TCL_ERROR;
}
}
break;
case TCL_NUMBER_BIG:
if (Tcl_GetBignumFromObj(interp, objv[1], &big) != TCL_OK) {
return TCL_ERROR;
}
if (big.sign) {
mp_clear(&big);
goto negarg;
}
break;
default:
if (TclGetWideIntFromObj(interp, objv[1], &w) != TCL_OK) {
return TCL_ERROR;
}
if (w < 0) {
goto negarg;
}
d = (double) w;
#ifdef IEEE_FLOATING_POINT
if (d < MAX_EXACT) {
exact = 1;
}
#endif
if (!exact) {
Tcl_GetBignumFromObj(interp, objv[1], &big);
}
break;
}
if (exact) {
Tcl_SetObjResult(interp, Tcl_NewWideIntObj((Tcl_WideInt) sqrt(d)));
} else {
mp_int root;
mp_init(&root);
mp_sqrt(&big, &root);
mp_clear(&big);
Tcl_SetObjResult(interp, Tcl_NewBignumObj(&root));
}
return TCL_OK;
negarg:
Tcl_SetObjResult(interp, Tcl_NewStringObj(
"square root of negative argument", -1));
Tcl_SetErrorCode(interp, "ARITH", "DOMAIN",
"domain error: argument not in valid range", NULL);
return TCL_ERROR;
}
static int
ExprSqrtFunc(
ClientData clientData, /* Ignored */
Tcl_Interp *interp, /* The interpreter in which to execute the
* function. */
int objc, /* Actual parameter count. */
Tcl_Obj *const *objv) /* Actual parameter list. */
{
int code;
double d;
mp_int big;
if (objc != 2) {
MathFuncWrongNumArgs(interp, 2, objc, objv);
return TCL_ERROR;
}
code = Tcl_GetDoubleFromObj(interp, objv[1], &d);
#ifdef ACCEPT_NAN
if ((code != TCL_OK) && (objv[1]->typePtr == &tclDoubleType)) {
Tcl_SetObjResult(interp, objv[1]);
return TCL_OK;
}
#endif
if (code != TCL_OK) {
return TCL_ERROR;
}
if ((d >= 0.0) && TclIsInfinite(d)
&& (Tcl_GetBignumFromObj(NULL, objv[1], &big) == TCL_OK)) {
mp_int root;
mp_init(&root);
mp_sqrt(&big, &root);
mp_clear(&big);
Tcl_SetObjResult(interp, Tcl_NewDoubleObj(TclBignumToDouble(&root)));
mp_clear(&root);
} else {
Tcl_SetObjResult(interp, Tcl_NewDoubleObj(sqrt(d)));
}
return TCL_OK;
}
static int
ExprUnaryFunc(
ClientData clientData, /* Contains the address of a function that
* takes one double argument and returns a
* double result. */
Tcl_Interp *interp, /* The interpreter in which to execute the
* function. */
int objc, /* Actual parameter count */
Tcl_Obj *const *objv) /* Actual parameter list */
{
int code;
double d;
double (*func)(double) = (double (*)(double)) clientData;
if (objc != 2) {
MathFuncWrongNumArgs(interp, 2, objc, objv);
return TCL_ERROR;
}
code = Tcl_GetDoubleFromObj(interp, objv[1], &d);
#ifdef ACCEPT_NAN
if ((code != TCL_OK) && (objv[1]->typePtr == &tclDoubleType)) {
d = objv[1]->internalRep.doubleValue;
Tcl_ResetResult(interp);
code = TCL_OK;
}
#endif
if (code != TCL_OK) {
return TCL_ERROR;
}
errno = 0;
return CheckDoubleResult(interp, func(d));
}
static int
CheckDoubleResult(
Tcl_Interp *interp,
double dResult)
{
#ifndef ACCEPT_NAN
if (TclIsNaN(dResult)) {
TclExprFloatError(interp, dResult);
return TCL_ERROR;
}
#endif
if ((errno == ERANGE) && ((dResult == 0.0) || TclIsInfinite(dResult))) {
/*
* When ERANGE signals under/overflow, just accept 0.0 or +/-Inf
*/
} else if (errno != 0) {
/*
* Report other errno values as errors.
*/
TclExprFloatError(interp, dResult);
return TCL_ERROR;
}
Tcl_SetObjResult(interp, Tcl_NewDoubleObj(dResult));
return TCL_OK;
}
static int
ExprBinaryFunc(
ClientData clientData, /* Contains the address of a function that
* takes two double arguments and returns a
* double result. */
Tcl_Interp *interp, /* The interpreter in which to execute the
* function. */
int objc, /* Actual parameter count. */
Tcl_Obj *const *objv) /* Parameter vector. */
{
int code;
double d1, d2;
double (*func)(double, double) = (double (*)(double, double)) clientData;
if (objc != 3) {
MathFuncWrongNumArgs(interp, 3, objc, objv);
return TCL_ERROR;
}
code = Tcl_GetDoubleFromObj(interp, objv[1], &d1);
#ifdef ACCEPT_NAN
if ((code != TCL_OK) && (objv[1]->typePtr == &tclDoubleType)) {
d1 = objv[1]->internalRep.doubleValue;
Tcl_ResetResult(interp);
code = TCL_OK;
}
#endif
if (code != TCL_OK) {
return TCL_ERROR;
}
code = Tcl_GetDoubleFromObj(interp, objv[2], &d2);
#ifdef ACCEPT_NAN
if ((code != TCL_OK) && (objv[2]->typePtr == &tclDoubleType)) {
d2 = objv[2]->internalRep.doubleValue;
Tcl_ResetResult(interp);
code = TCL_OK;
}
#endif
if (code != TCL_OK) {
return TCL_ERROR;
}
errno = 0;
return CheckDoubleResult(interp, func(d1, d2));
}
static int
ExprAbsFunc(
ClientData clientData, /* Ignored. */
Tcl_Interp *interp, /* The interpreter in which to execute the
* function. */
int objc, /* Actual parameter count. */
Tcl_Obj *const *objv) /* Parameter vector. */
{
ClientData ptr;
int type;
mp_int big;
if (objc != 2) {
MathFuncWrongNumArgs(interp, 2, objc, objv);
return TCL_ERROR;
}
if (TclGetNumberFromObj(interp, objv[1], &ptr, &type) != TCL_OK) {
return TCL_ERROR;
}
if (type == TCL_NUMBER_LONG) {
long l = *((const long *) ptr);
if (l > (long)0) {
goto unChanged;
} else if (l == (long)0) {
const char *string = objv[1]->bytes;
if (string) {
while (*string != '0') {
if (*string == '-') {
Tcl_SetObjResult(interp, Tcl_NewLongObj(0));
return TCL_OK;
}
string++;
}
}
goto unChanged;
} else if (l == LONG_MIN) {
TclBNInitBignumFromLong(&big, l);
goto tooLarge;
}
Tcl_SetObjResult(interp, Tcl_NewLongObj(-l));
return TCL_OK;
}
if (type == TCL_NUMBER_DOUBLE) {
double d = *((const double *) ptr);
static const double poszero = 0.0;
/*
* We need to distinguish here between positive 0.0 and negative -0.0.
* [Bug 2954959]
*/
if (d == -0.0) {
if (!memcmp(&d, &poszero, sizeof(double))) {
goto unChanged;
}
} else if (d > -0.0) {
goto unChanged;
}
Tcl_SetObjResult(interp, Tcl_NewDoubleObj(-d));
return TCL_OK;
}
#ifndef TCL_WIDE_INT_IS_LONG
if (type == TCL_NUMBER_WIDE) {
Tcl_WideInt w = *((const Tcl_WideInt *) ptr);
if (w >= (Tcl_WideInt)0) {
goto unChanged;
}
if (w == LLONG_MIN) {
TclBNInitBignumFromWideInt(&big, w);
goto tooLarge;
}
Tcl_SetObjResult(interp, Tcl_NewWideIntObj(-w));
return TCL_OK;
}
#endif
if (type == TCL_NUMBER_BIG) {
if (mp_cmp_d((const mp_int *) ptr, 0) == MP_LT) {
Tcl_GetBignumFromObj(NULL, objv[1], &big);
tooLarge:
(void)mp_neg(&big, &big);
Tcl_SetObjResult(interp, Tcl_NewBignumObj(&big));
} else {
unChanged:
Tcl_SetObjResult(interp, objv[1]);
}
return TCL_OK;
}
if (type == TCL_NUMBER_NAN) {
#ifdef ACCEPT_NAN
Tcl_SetObjResult(interp, objv[1]);
return TCL_OK;
#else
double d;
Tcl_GetDoubleFromObj(interp, objv[1], &d);
return TCL_ERROR;
#endif
}
return TCL_OK;
}
static int
ExprBoolFunc(
ClientData clientData, /* Ignored. */
Tcl_Interp *interp, /* The interpreter in which to execute the
* function. */
int objc, /* Actual parameter count. */
Tcl_Obj *const *objv) /* Actual parameter vector. */
{
int value;
if (objc != 2) {
MathFuncWrongNumArgs(interp, 2, objc, objv);
return TCL_ERROR;
}
if (Tcl_GetBooleanFromObj(interp, objv[1], &value) != TCL_OK) {
return TCL_ERROR;
}
Tcl_SetObjResult(interp, Tcl_NewBooleanObj(value));
return TCL_OK;
}
static int
ExprDoubleFunc(
ClientData clientData, /* Ignored. */
Tcl_Interp *interp, /* The interpreter in which to execute the
* function. */
int objc, /* Actual parameter count. */
Tcl_Obj *const *objv) /* Actual parameter vector. */
{
double dResult;
if (objc != 2) {
MathFuncWrongNumArgs(interp, 2, objc, objv);
return TCL_ERROR;
}
if (Tcl_GetDoubleFromObj(interp, objv[1], &dResult) != TCL_OK) {
#ifdef ACCEPT_NAN
if (objv[1]->typePtr == &tclDoubleType) {
Tcl_SetObjResult(interp, objv[1]);
return TCL_OK;
}
#endif
return TCL_ERROR;
}
Tcl_SetObjResult(interp, Tcl_NewDoubleObj(dResult));
return TCL_OK;
}
static int
ExprEntierFunc(
ClientData clientData, /* Ignored. */
Tcl_Interp *interp, /* The interpreter in which to execute the
* function. */
int objc, /* Actual parameter count. */
Tcl_Obj *const *objv) /* Actual parameter vector. */
{
double d;
int type;
ClientData ptr;
if (objc != 2) {
MathFuncWrongNumArgs(interp, 2, objc, objv);
return TCL_ERROR;
}
if (TclGetNumberFromObj(interp, objv[1], &ptr, &type) != TCL_OK) {
return TCL_ERROR;
}
if (type == TCL_NUMBER_DOUBLE) {
d = *((const double *) ptr);
if ((d < (double)LONG_MAX) && (d > (double)LONG_MIN)) {
long result = (long) d;
Tcl_SetObjResult(interp, Tcl_NewLongObj(result));
return TCL_OK;
#ifndef TCL_WIDE_INT_IS_LONG
} else if ((d < (double)LLONG_MAX) && (d > (double)LLONG_MIN)) {
Tcl_WideInt result = (Tcl_WideInt) d;
Tcl_SetObjResult(interp, Tcl_NewWideIntObj(result));
return TCL_OK;
#endif
} else {
mp_int big;
if (Tcl_InitBignumFromDouble(interp, d, &big) != TCL_OK) {
/* Infinity */
return TCL_ERROR;
}
Tcl_SetObjResult(interp, Tcl_NewBignumObj(&big));
return TCL_OK;
}
}
if (type != TCL_NUMBER_NAN) {
/*
* All integers are already of integer type.
*/
Tcl_SetObjResult(interp, objv[1]);
return TCL_OK;
}
/*
* Get the error message for NaN.
*/
Tcl_GetDoubleFromObj(interp, objv[1], &d);
return TCL_ERROR;
}
static int
ExprIntFunc(
ClientData clientData, /* Ignored. */
Tcl_Interp *interp, /* The interpreter in which to execute the
* function. */
int objc, /* Actual parameter count. */
Tcl_Obj *const *objv) /* Actual parameter vector. */
{
long iResult;
Tcl_Obj *objPtr;
if (ExprEntierFunc(NULL, interp, objc, objv) != TCL_OK) {
return TCL_ERROR;
}
objPtr = Tcl_GetObjResult(interp);
if (TclGetLongFromObj(NULL, objPtr, &iResult) != TCL_OK) {
/*
* Truncate the bignum; keep only bits in long range.
*/
mp_int big;
Tcl_GetBignumFromObj(NULL, objPtr, &big);
mp_mod_2d(&big, (int) CHAR_BIT * sizeof(long), &big);
objPtr = Tcl_NewBignumObj(&big);
Tcl_IncrRefCount(objPtr);
TclGetLongFromObj(NULL, objPtr, &iResult);
Tcl_DecrRefCount(objPtr);
}
Tcl_SetObjResult(interp, Tcl_NewLongObj(iResult));
return TCL_OK;
}
static int
ExprWideFunc(
ClientData clientData, /* Ignored. */
Tcl_Interp *interp, /* The interpreter in which to execute the
* function. */
int objc, /* Actual parameter count. */
Tcl_Obj *const *objv) /* Actual parameter vector. */
{
Tcl_WideInt wResult;
Tcl_Obj *objPtr;
if (ExprEntierFunc(NULL, interp, objc, objv) != TCL_OK) {
return TCL_ERROR;
}
objPtr = Tcl_GetObjResult(interp);
if (TclGetWideIntFromObj(NULL, objPtr, &wResult) != TCL_OK) {
/*
* Truncate the bignum; keep only bits in wide int range.
*/
mp_int big;
Tcl_GetBignumFromObj(NULL, objPtr, &big);
mp_mod_2d(&big, (int) CHAR_BIT * sizeof(Tcl_WideInt), &big);
objPtr = Tcl_NewBignumObj(&big);
Tcl_IncrRefCount(objPtr);
TclGetWideIntFromObj(NULL, objPtr, &wResult);
Tcl_DecrRefCount(objPtr);
}
Tcl_SetObjResult(interp, Tcl_NewWideIntObj(wResult));
return TCL_OK;
}
static int
ExprRandFunc(
ClientData clientData, /* Ignored. */
Tcl_Interp *interp, /* The interpreter in which to execute the
* function. */
int objc, /* Actual parameter count. */
Tcl_Obj *const *objv) /* Actual parameter vector. */
{
Interp *iPtr = (Interp *) interp;
double dResult;
long tmp; /* Algorithm assumes at least 32 bits. Only
* long guarantees that. See below. */
Tcl_Obj *oResult;
if (objc != 1) {
MathFuncWrongNumArgs(interp, 1, objc, objv);
return TCL_ERROR;
}
if (!(iPtr->flags & RAND_SEED_INITIALIZED)) {
iPtr->flags |= RAND_SEED_INITIALIZED;
/*
* To ensure different seeds in different threads (bug #416643),
* take into consideration the thread this interp is running in.
*/
iPtr->randSeed = TclpGetClicks() + (PTR2INT(Tcl_GetCurrentThread())<<12);
/*
* Make sure 1 <= randSeed <= (2^31) - 2. See below.
*/
iPtr->randSeed &= (unsigned long) 0x7FFFFFFF;
if ((iPtr->randSeed == 0) || (iPtr->randSeed == 0x7FFFFFFF)) {
iPtr->randSeed ^= 123459876;
}
}
/*
* Generate the random number using the linear congruential generator
* defined by the following recurrence:
* seed = ( IA * seed ) mod IM
* where IA is 16807 and IM is (2^31) - 1. The recurrence maps a seed in
* the range [1, IM - 1] to a new seed in that same range. The recurrence
* maps IM to 0, and maps 0 back to 0, so those two values must not be
* allowed as initial values of seed.
*
* In order to avoid potential problems with integer overflow, the
* recurrence is implemented in terms of additional constants IQ and IR
* such that
* IM = IA*IQ + IR
* None of the operations in the implementation overflows a 32-bit signed
* integer, and the C type long is guaranteed to be at least 32 bits wide.
*
* For more details on how this algorithm works, refer to the following
* papers:
*
* S.K. Park & K.W. Miller, "Random number generators: good ones are hard
* to find," Comm ACM 31(10):1192-1201, Oct 1988
*
* W.H. Press & S.A. Teukolsky, "Portable random number generators,"
* Computers in Physics 6(5):522-524, Sep/Oct 1992.
*/
#define RAND_IA 16807
#define RAND_IM 2147483647
#define RAND_IQ 127773
#define RAND_IR 2836
#define RAND_MASK 123459876
tmp = iPtr->randSeed/RAND_IQ;
iPtr->randSeed = RAND_IA*(iPtr->randSeed - tmp*RAND_IQ) - RAND_IR*tmp;
if (iPtr->randSeed < 0) {
iPtr->randSeed += RAND_IM;
}
/*
* Since the recurrence keeps seed values in the range [1, RAND_IM - 1],
* dividing by RAND_IM yields a double in the range (0, 1).
*/
dResult = iPtr->randSeed * (1.0/RAND_IM);
/*
* Push a Tcl object with the result.
*/
TclNewDoubleObj(oResult, dResult);
Tcl_SetObjResult(interp, oResult);
return TCL_OK;
}
static int
ExprRoundFunc(
ClientData clientData, /* Ignored. */
Tcl_Interp *interp, /* The interpreter in which to execute the
* function. */
int objc, /* Actual parameter count. */
Tcl_Obj *const *objv) /* Parameter vector. */
{
double d;
ClientData ptr;
int type;
if (objc != 2) {
MathFuncWrongNumArgs(interp, 2, objc, objv);
return TCL_ERROR;
}
if (TclGetNumberFromObj(interp, objv[1], &ptr, &type) != TCL_OK) {
return TCL_ERROR;
}
if (type == TCL_NUMBER_DOUBLE) {
double fractPart, intPart;
long max = LONG_MAX, min = LONG_MIN;
fractPart = modf(*((const double *) ptr), &intPart);
if (fractPart <= -0.5) {
min++;
} else if (fractPart >= 0.5) {
max--;
}
if ((intPart >= (double)max) || (intPart <= (double)min)) {
mp_int big;
if (Tcl_InitBignumFromDouble(interp, intPart, &big) != TCL_OK) {
/* Infinity */
return TCL_ERROR;
}
if (fractPart <= -0.5) {
mp_sub_d(&big, 1, &big);
} else if (fractPart >= 0.5) {
mp_add_d(&big, 1, &big);
}
Tcl_SetObjResult(interp, Tcl_NewBignumObj(&big));
return TCL_OK;
} else {
long result = (long)intPart;
if (fractPart <= -0.5) {
result--;
} else if (fractPart >= 0.5) {
result++;
}
Tcl_SetObjResult(interp, Tcl_NewLongObj(result));
return TCL_OK;
}
}
if (type != TCL_NUMBER_NAN) {
/*
* All integers are already rounded
*/
Tcl_SetObjResult(interp, objv[1]);
return TCL_OK;
}
/*
* Get the error message for NaN.
*/
Tcl_GetDoubleFromObj(interp, objv[1], &d);
return TCL_ERROR;
}
static int
ExprSrandFunc(
ClientData clientData, /* Ignored. */
Tcl_Interp *interp, /* The interpreter in which to execute the
* function. */
int objc, /* Actual parameter count. */
Tcl_Obj *const *objv) /* Parameter vector. */
{
Interp *iPtr = (Interp *) interp;
long i = 0; /* Initialized to avoid compiler warning. */
/*
* Convert argument and use it to reset the seed.
*/
if (objc != 2) {
MathFuncWrongNumArgs(interp, 2, objc, objv);
return TCL_ERROR;
}
if (TclGetLongFromObj(NULL, objv[1], &i) != TCL_OK) {
Tcl_Obj *objPtr;
mp_int big;
if (Tcl_GetBignumFromObj(interp, objv[1], &big) != TCL_OK) {
/* TODO: more ::errorInfo here? or in caller? */
return TCL_ERROR;
}
mp_mod_2d(&big, (int) CHAR_BIT * sizeof(long), &big);
objPtr = Tcl_NewBignumObj(&big);
Tcl_IncrRefCount(objPtr);
TclGetLongFromObj(NULL, objPtr, &i);
Tcl_DecrRefCount(objPtr);
}
/*
* Reset the seed. Make sure 1 <= randSeed <= 2^31 - 2. See comments in
* ExprRandFunc for more details.
*/
iPtr->flags |= RAND_SEED_INITIALIZED;
iPtr->randSeed = i;
iPtr->randSeed &= (unsigned long) 0x7FFFFFFF;
if ((iPtr->randSeed == 0) || (iPtr->randSeed == 0x7FFFFFFF)) {
iPtr->randSeed ^= 123459876;
}
/*
* To avoid duplicating the random number generation code we simply clean
* up our state and call the real random number function. That function
* will always succeed.
*/
return ExprRandFunc(clientData, interp, 1, objv);
}
/*
*----------------------------------------------------------------------
*
* MathFuncWrongNumArgs --
*
* Generate an error message when a math function presents the wrong
* number of arguments.
*
* Results:
* None.
*
* Side effects:
* An error message is stored in the interpreter result.
*
*----------------------------------------------------------------------
*/
static void
MathFuncWrongNumArgs(
Tcl_Interp *interp, /* Tcl interpreter */
int expected, /* Formal parameter count. */
int found, /* Actual parameter count. */
Tcl_Obj *const *objv) /* Actual parameter vector. */
{
const char *name = Tcl_GetString(objv[0]);
const char *tail = name + strlen(name);
while (tail > name+1) {
tail--;
if (*tail == ':' && tail[-1] == ':') {
name = tail+1;
break;
}
}
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
"%s arguments for math function \"%s\"",
(found < expected ? "not enough" : "too many"), name));
Tcl_SetErrorCode(interp, "TCL", "WRONGARGS", NULL);
}
#ifdef USE_DTRACE
/*
*----------------------------------------------------------------------
*
* DTraceObjCmd --
*
* This function is invoked to process the "::tcl::dtrace" Tcl command.
*
* Results:
* A standard Tcl object result.
*
* Side effects:
* The 'tcl-probe' DTrace probe is triggered (if it is enabled).
*
*----------------------------------------------------------------------
*/
static int
DTraceObjCmd(
ClientData dummy, /* Not used. */
Tcl_Interp *interp, /* Current interpreter. */
int objc, /* Number of arguments. */
Tcl_Obj *const objv[]) /* Argument objects. */
{
if (TCL_DTRACE_TCL_PROBE_ENABLED()) {
char *a[10];
int i = 0;
while (i++ < 10) {
a[i-1] = i < objc ? TclGetString(objv[i]) : NULL;
}
TCL_DTRACE_TCL_PROBE(a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7],
a[8], a[9]);
}
return TCL_OK;
}
/*
*----------------------------------------------------------------------
*
* TclDTraceInfo --
*
* Extract information from a TIP280 dict for use by DTrace probes.
*
* Results:
* None.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
void
TclDTraceInfo(
Tcl_Obj *info,
const char **args,
int *argsi)
{
static Tcl_Obj *keys[10] = { NULL };
Tcl_Obj **k = keys, *val;
int i = 0;
if (!*k) {
#define kini(s) TclNewLiteralStringObj(keys[i], s); i++
kini("cmd"); kini("type"); kini("proc"); kini("file");
kini("method"); kini("class"); kini("lambda"); kini("object");
kini("line"); kini("level");
#undef kini
}
for (i = 0; i < 6; i++) {
Tcl_DictObjGet(NULL, info, *k++, &val);
args[i] = val ? TclGetString(val) : NULL;
}
/*
* no "proc" -> use "lambda"
*/
if (!args[2]) {
Tcl_DictObjGet(NULL, info, *k, &val);
args[2] = val ? TclGetString(val) : NULL;
}
k++;
/*
* no "class" -> use "object"
*/
if (!args[5]) {
Tcl_DictObjGet(NULL, info, *k, &val);
args[5] = val ? TclGetString(val) : NULL;
}
k++;
for (i = 0; i < 2; i++) {
Tcl_DictObjGet(NULL, info, *k++, &val);
if (val) {
TclGetIntFromObj(NULL, val, &argsi[i]);
} else {
argsi[i] = 0;
}
}
}
/*
*----------------------------------------------------------------------
*
* DTraceCmdReturn --
*
* NR callback for DTrace command return probes.
*
* Results:
* None.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
static int
DTraceCmdReturn(
ClientData data[],
Tcl_Interp *interp,
int result)
{
char *cmdName = TclGetString((Tcl_Obj *) data[0]);
if (TCL_DTRACE_CMD_RETURN_ENABLED()) {
TCL_DTRACE_CMD_RETURN(cmdName, result);
}
if (TCL_DTRACE_CMD_RESULT_ENABLED()) {
Tcl_Obj *r = Tcl_GetObjResult(interp);
TCL_DTRACE_CMD_RESULT(cmdName, result, TclGetString(r), r);
}
return result;
}
TCL_DTRACE_DEBUG_LOG()
#endif /* USE_DTRACE */
/*
*----------------------------------------------------------------------
*
* Tcl_NRCallObjProc --
*
* This function calls an objProc directly while managing things properly
* if it happens to be an NR objProc. It is meant to be used by extenders
* that provide an NR implementation of a command, as this function
* permits a trivial coding of the non-NR objProc.
*
* Results:
* The return value is a standard Tcl completion code such as TCL_OK or
* TCL_ERROR. A result or error message is left in interp's result.
*
* Side effects:
* Depends on the objProc.
*
*----------------------------------------------------------------------
*/
int
Tcl_NRCallObjProc(
Tcl_Interp *interp,
Tcl_ObjCmdProc *objProc,
ClientData clientData,
int objc,
Tcl_Obj *const objv[])
{
NRE_callback *rootPtr = TOP_CB(interp);
TclNRAddCallback(interp, Dispatch, objProc, clientData,
INT2PTR(objc), objv);
return TclNRRunCallbacks(interp, TCL_OK, rootPtr);
}
/*
*----------------------------------------------------------------------
*
* Tcl_NRCreateCommand --
*
* Define a new NRE-enabled object-based command in a command table.
*
* Results:
* The return value is a token for the command, which can be used in
* future calls to Tcl_GetCommandName.
*
* Side effects:
* If no command named "cmdName" already exists for interp, one is
* created. Otherwise, if a command does exist, then if the object-based
* Tcl_ObjCmdProc is TclInvokeStringCommand, we assume Tcl_CreateCommand
* was called previously for the same command and just set its
* Tcl_ObjCmdProc to the argument "proc"; otherwise, we delete the old
* command.
*
* In the future, during bytecode evaluation when "cmdName" is seen as
* the name of a command by Tcl_EvalObj or Tcl_Eval, the object-based
* Tcl_ObjCmdProc proc will be called. When the command is deleted from
* the table, deleteProc will be called. See the manual entry for details
* on the calling sequence.
*
*----------------------------------------------------------------------
*/
Tcl_Command
Tcl_NRCreateCommand(
Tcl_Interp *interp, /* Token for command interpreter (returned by
* previous call to Tcl_CreateInterp). */
const char *cmdName, /* Name of command. If it contains namespace
* qualifiers, the new command is put in the
* specified namespace; otherwise it is put in
* the global namespace. */
Tcl_ObjCmdProc *proc, /* Object-based function to associate with
* name, provides direct access for direct
* calls. */
Tcl_ObjCmdProc *nreProc, /* Object-based function to associate with
* name, provides NR implementation */
ClientData clientData, /* Arbitrary value to pass to object
* function. */
Tcl_CmdDeleteProc *deleteProc)
/* If not NULL, gives a function to call when
* this command is deleted. */
{
Command *cmdPtr = (Command *)
Tcl_CreateObjCommand(interp,cmdName,proc,clientData,deleteProc);
cmdPtr->nreProc = nreProc;
return (Tcl_Command) cmdPtr;
}
Tcl_Command
TclNRCreateCommandInNs (
Tcl_Interp *interp,
const char *cmdName,
Tcl_Namespace *nsPtr,
Tcl_ObjCmdProc *proc,
Tcl_ObjCmdProc *nreProc,
ClientData clientData,
Tcl_CmdDeleteProc *deleteProc) {
Command *cmdPtr = (Command *)
TclCreateObjCommandInNs(interp,cmdName,nsPtr,proc,clientData,deleteProc);
cmdPtr->nreProc = nreProc;
return (Tcl_Command) cmdPtr;
}
/****************************************************************************
* Stuff for the public api
****************************************************************************/
int
Tcl_NREvalObj(
Tcl_Interp *interp,
Tcl_Obj *objPtr,
int flags)
{
return TclNREvalObjEx(interp, objPtr, flags, NULL, INT_MIN);
}
int
Tcl_NREvalObjv(
Tcl_Interp *interp, /* Interpreter in which to evaluate the
* command. Also used for error reporting. */
int objc, /* Number of words in command. */
Tcl_Obj *const objv[], /* An array of pointers to objects that are
* the words that make up the command. */
int flags) /* Collection of OR-ed bits that control the
* evaluation of the script. Only
* TCL_EVAL_GLOBAL, TCL_EVAL_INVOKE and
* TCL_EVAL_NOERR are currently supported. */
{
return TclNREvalObjv(interp, objc, objv, flags, NULL);
}
int
Tcl_NRCmdSwap(
Tcl_Interp *interp,
Tcl_Command cmd,
int objc,
Tcl_Obj *const objv[],
int flags)
{
return TclNREvalObjv(interp, objc, objv, flags|TCL_EVAL_NOERR,
(Command *) cmd);
}
/*****************************************************************************
* Tailcall related code
*****************************************************************************
*
* The steps of the tailcall dance are as follows:
*
* 1. when [tailcall] is invoked, it stores the corresponding callback in
* the current CallFrame and returns TCL_RETURN
* 2. when the CallFrame is popped, it calls TclSetTailcall to store the
* callback in the proper NRCommand callback - the spot where the command
* that pushed the CallFrame is completely cleaned up
* 3. when the NRCommand callback runs, it schedules the tailcall callback
* to run immediately after it returns
*
* One delicate point is to properly define the NRCommand where the tailcall
* will execute. There are functions whose purpose is to help define the
* precise spot:
* TclMarkTailcall: if the NEXT command to be pushed tailcalls, execution
* should continue right here
* TclSkipTailcall: if the NEXT command to be pushed tailcalls, execution
* should continue after the CURRENT command is fully returned ("skip
* the next command: we are redirecting to it, tailcalls should run
* after WE return")
* TclPushTailcallPoint: the search for a tailcalling spot cannot traverse
* this point. This is special for OO, as some of the oo constructs
* that behave like commands may not push an NRCommand callback.
*/
void
TclMarkTailcall(
Tcl_Interp *interp)
{
Interp *iPtr = (Interp *) interp;
if (iPtr->deferredCallbacks == NULL) {
TclNRAddCallback(interp, NRCommand, NULL, NULL,
NULL, NULL);
iPtr->deferredCallbacks = TOP_CB(interp);
}
}
void
TclSkipTailcall(
Tcl_Interp *interp)
{
Interp *iPtr = (Interp *) interp;
TclMarkTailcall(interp);
iPtr->deferredCallbacks->data[1] = INT2PTR(1);
}
void
TclPushTailcallPoint(
Tcl_Interp *interp)
{
TclNRAddCallback(interp, NRCommand, NULL, NULL, NULL, NULL);
((Interp *) interp)->numLevels++;
}
/*
*----------------------------------------------------------------------
*
* TclSetTailcall --
*
* Splice a tailcall command in the proper spot of the NRE callback
* stack, so that it runs at the right time.
*
*----------------------------------------------------------------------
*/
void
TclSetTailcall(
Tcl_Interp *interp,
Tcl_Obj *listPtr)
{
/*
* Find the splicing spot: right before the NRCommand of the thing
* being tailcalled. Note that we skip NRCommands marked by a 1 in data[1]
* (used by command redirectors).
*/
NRE_callback *runPtr;
for (runPtr = TOP_CB(interp); runPtr; runPtr = runPtr->nextPtr) {
if (((runPtr->procPtr) == NRCommand) && !runPtr->data[1]) {
break;
}
}
if (!runPtr) {
Tcl_Panic("tailcall cannot find the right splicing spot: should not happen!");
}
runPtr->data[1] = listPtr;
}
/*
*----------------------------------------------------------------------
*
* TclNRTailcallObjCmd --
*
* Prepare the tailcall as a list and store it in the current
* varFrame. When the frame is later popped the tailcall will be spliced
* at the proper place.
*
* Results:
* The first NRCommand callback that is not marked to be skipped is
* updated so that its data[1] field contains the tailcall list.
*
*----------------------------------------------------------------------
*/
int
TclNRTailcallObjCmd(
ClientData clientData,
Tcl_Interp *interp,
int objc,
Tcl_Obj *const objv[])
{
Interp *iPtr = (Interp *) interp;
if (objc < 1) {
Tcl_WrongNumArgs(interp, 1, objv, "?command? ?arg ...?");
return TCL_ERROR;
}
if (!(iPtr->varFramePtr->isProcCallFrame & 1)) {
Tcl_SetObjResult(interp, Tcl_NewStringObj(
"tailcall can only be called from a proc, lambda or method", -1));
Tcl_SetErrorCode(interp, "TCL", "TAILCALL", "ILLEGAL", NULL);
return TCL_ERROR;
}
/*
* Invocation without args just clears a scheduled tailcall; invocation
* with an argument replaces any previously scheduled tailcall.
*/
if (iPtr->varFramePtr->tailcallPtr) {
Tcl_DecrRefCount(iPtr->varFramePtr->tailcallPtr);
iPtr->varFramePtr->tailcallPtr = NULL;
}
/*
* Create the callback to actually evaluate the tailcalled
* command, then set it in the varFrame so that PopCallFrame can use it
* at the proper time.
*/
if (objc > 1) {
Tcl_Obj *listPtr, *nsObjPtr;
Tcl_Namespace *nsPtr = (Tcl_Namespace *) iPtr->varFramePtr->nsPtr;
/*
* The tailcall data is in a Tcl list: the first element is the
* namespace, the rest the command to be tailcalled.
*/
nsObjPtr = Tcl_NewStringObj(nsPtr->fullName, -1);
listPtr = Tcl_NewListObj(objc, objv);
TclListObjSetElement(interp, listPtr, 0, nsObjPtr);
iPtr->varFramePtr->tailcallPtr = listPtr;
}
return TCL_RETURN;
}
/*
*----------------------------------------------------------------------
*
* TclNRTailcallEval --
*
* This NREcallback actually causes the tailcall to be evaluated.
*
*----------------------------------------------------------------------
*/
int
TclNRTailcallEval(
ClientData data[],
Tcl_Interp *interp,
int result)
{
Interp *iPtr = (Interp *) interp;
Tcl_Obj *listPtr = data[0], *nsObjPtr;
Tcl_Namespace *nsPtr;
int objc;
Tcl_Obj **objv;
Tcl_ListObjGetElements(interp, listPtr, &objc, &objv);
nsObjPtr = objv[0];
if (result == TCL_OK) {
result = TclGetNamespaceFromObj(interp, nsObjPtr, &nsPtr);
}
if (result != TCL_OK) {
/*
* Tailcall execution was preempted, eg by an intervening catch or by
* a now-gone namespace: cleanup and return.
*/
Tcl_DecrRefCount(listPtr);
return result;
}
/*
* Perform the tailcall
*/
TclMarkTailcall(interp);
TclNRAddCallback(interp, TclNRReleaseValues, listPtr, NULL, NULL,NULL);
iPtr->lookupNsPtr = (Namespace *) nsPtr;
return TclNREvalObjv(interp, objc-1, objv+1, 0, NULL);
}
int
TclNRReleaseValues(
ClientData data[],
Tcl_Interp *interp,
int result)
{
int i = 0;
while (i < 4) {
if (data[i]) {
Tcl_DecrRefCount((Tcl_Obj *) data[i]);
} else {
break;
}
i++;
}
return result;
}
void
Tcl_NRAddCallback(
Tcl_Interp *interp,
Tcl_NRPostProc *postProcPtr,
ClientData data0,
ClientData data1,
ClientData data2,
ClientData data3)
{
if (!(postProcPtr)) {
Tcl_Panic("Adding a callback without an objProc?!");
}
TclNRAddCallback(interp, postProcPtr, data0, data1, data2, data3);
}
/*
*----------------------------------------------------------------------
*
* TclNRCoroutineObjCmd -- (and friends)
*
* This object-based function is invoked to process the "coroutine" Tcl
* command. It is heavily based on "apply".
*
* Results:
* A standard Tcl object result value.
*
* Side effects:
* A new procedure gets created.
*
* ** FIRST EXPERIMENTAL IMPLEMENTATION **
*
* It is fairly amateurish and not up to our standards - mainly in terms of
* error messages and [info] interaction. Just to test the infrastructure in
* teov and tebc.
*----------------------------------------------------------------------
*/
#define iPtr ((Interp *) interp)
int
TclNRYieldObjCmd(
ClientData clientData,
Tcl_Interp *interp,
int objc,
Tcl_Obj *const objv[])
{
CoroutineData *corPtr = iPtr->execEnvPtr->corPtr;
if (objc > 2) {
Tcl_WrongNumArgs(interp, 1, objv, "?returnValue?");
return TCL_ERROR;
}
if (!corPtr) {
Tcl_SetObjResult(interp, Tcl_NewStringObj(
"yield can only be called in a coroutine", -1));
Tcl_SetErrorCode(interp, "TCL", "COROUTINE", "ILLEGAL_YIELD", NULL);
return TCL_ERROR;
}
if (objc == 2) {
Tcl_SetObjResult(interp, objv[1]);
}
NRE_ASSERT(!COR_IS_SUSPENDED(corPtr));
TclNRAddCallback(interp, TclNRCoroutineActivateCallback, corPtr,
clientData, NULL, NULL);
return TCL_OK;
}
int
TclNRYieldToObjCmd(
ClientData clientData,
Tcl_Interp *interp,
int objc,
Tcl_Obj *const objv[])
{
CoroutineData *corPtr = iPtr->execEnvPtr->corPtr;
Tcl_Obj *listPtr, *nsObjPtr;
Tcl_Namespace *nsPtr = TclGetCurrentNamespace(interp);
if (objc < 2) {
Tcl_WrongNumArgs(interp, 1, objv, "command ?arg ...?");
return TCL_ERROR;
}
if (!corPtr) {
Tcl_SetObjResult(interp, Tcl_NewStringObj(
"yieldto can only be called in a coroutine", -1));
Tcl_SetErrorCode(interp, "TCL", "COROUTINE", "ILLEGAL_YIELD", NULL);
return TCL_ERROR;
}
if (((Namespace *) nsPtr)->flags & NS_DYING) {
Tcl_SetObjResult(interp, Tcl_NewStringObj(
"yieldto called in deleted namespace", -1));
Tcl_SetErrorCode(interp, "TCL", "COROUTINE", "YIELDTO_IN_DELETED",
NULL);
return TCL_ERROR;
}
/*
* Add the tailcall in the caller env, then just yield.
*
* This is essentially code from TclNRTailcallObjCmd
*/
listPtr = Tcl_NewListObj(objc, objv);
nsObjPtr = Tcl_NewStringObj(nsPtr->fullName, -1);
TclListObjSetElement(interp, listPtr, 0, nsObjPtr);
/*
* Add the callback in the caller's env, then instruct TEBC to yield.
*/
iPtr->execEnvPtr = corPtr->callerEEPtr;
TclSetTailcall(interp, listPtr);
iPtr->execEnvPtr = corPtr->eePtr;
return TclNRYieldObjCmd(INT2PTR(CORO_ACTIVATE_YIELDM), interp, 1, objv);
}
static int
RewindCoroutineCallback(
ClientData data[],
Tcl_Interp *interp,
int result)
{
return Tcl_RestoreInterpState(interp, data[0]);
}
static int
RewindCoroutine(
CoroutineData *corPtr,
int result)
{
Tcl_Interp *interp = corPtr->eePtr->interp;
Tcl_InterpState state = Tcl_SaveInterpState(interp, result);
NRE_ASSERT(COR_IS_SUSPENDED(corPtr));
NRE_ASSERT(corPtr->eePtr != NULL);
NRE_ASSERT(corPtr->eePtr != iPtr->execEnvPtr);
corPtr->eePtr->rewind = 1;
TclNRAddCallback(interp, RewindCoroutineCallback, state,
NULL, NULL, NULL);
return TclNRInterpCoroutine(corPtr, interp, 0, NULL);
}
static void
DeleteCoroutine(
ClientData clientData)
{
CoroutineData *corPtr = clientData;
Tcl_Interp *interp = corPtr->eePtr->interp;
NRE_callback *rootPtr = TOP_CB(interp);
if (COR_IS_SUSPENDED(corPtr)) {
TclNRRunCallbacks(interp, RewindCoroutine(corPtr,TCL_OK), rootPtr);
}
}
static int
NRCoroutineCallerCallback(
ClientData data[],
Tcl_Interp *interp,
int result)
{
CoroutineData *corPtr = data[0];
Command *cmdPtr = corPtr->cmdPtr;
/*
* This is the last callback in the caller execEnv, right before switching
* to the coroutine's
*/
NRE_ASSERT(iPtr->execEnvPtr == corPtr->callerEEPtr);
if (!corPtr->eePtr) {
/*
* The execEnv was wound down but not deleted for our sake. We finish
* the job here. The caller context has already been restored.
*/
NRE_ASSERT(iPtr->varFramePtr == corPtr->caller.varFramePtr);
NRE_ASSERT(iPtr->framePtr == corPtr->caller.framePtr);
NRE_ASSERT(iPtr->cmdFramePtr == corPtr->caller.cmdFramePtr);
ckfree(corPtr);
return result;
}
NRE_ASSERT(COR_IS_SUSPENDED(corPtr));
SAVE_CONTEXT(corPtr->running);
RESTORE_CONTEXT(corPtr->caller);
if (cmdPtr->flags & CMD_IS_DELETED) {
/*
* The command was deleted while it was running: wind down the
* execEnv, this will do the complete cleanup. RewindCoroutine will
* restore both the caller's context and interp state.
*/
return RewindCoroutine(corPtr, result);
}
return result;
}
static int
NRCoroutineExitCallback(
ClientData data[],
Tcl_Interp *interp,
int result)
{
CoroutineData *corPtr = data[0];
Command *cmdPtr = corPtr->cmdPtr;
/*
* This runs at the bottom of the Coroutine's execEnv: it will be executed
* when the coroutine returns or is wound down, but not when it yields. It
* deletes the coroutine and restores the caller's environment.
*/
NRE_ASSERT(interp == corPtr->eePtr->interp);
NRE_ASSERT(TOP_CB(interp) == NULL);
NRE_ASSERT(iPtr->execEnvPtr == corPtr->eePtr);
NRE_ASSERT(!COR_IS_SUSPENDED(corPtr));
NRE_ASSERT((corPtr->callerEEPtr->callbackPtr->procPtr == NRCoroutineCallerCallback));
cmdPtr->deleteProc = NULL;
Tcl_DeleteCommandFromToken(interp, (Tcl_Command) cmdPtr);
TclCleanupCommandMacro(cmdPtr);
corPtr->eePtr->corPtr = NULL;
TclDeleteExecEnv(corPtr->eePtr);
corPtr->eePtr = NULL;
corPtr->stackLevel = NULL;
/*
* #280.
* Drop the coroutine-owned copy of the lineLABCPtr hashtable for literal
* command arguments in bytecode.
*/
Tcl_DeleteHashTable(corPtr->lineLABCPtr);
ckfree(corPtr->lineLABCPtr);
corPtr->lineLABCPtr = NULL;
RESTORE_CONTEXT(corPtr->caller);
iPtr->execEnvPtr = corPtr->callerEEPtr;
iPtr->numLevels++;
return result;
}
/*
*----------------------------------------------------------------------
*
* TclNRCoroutineActivateCallback --
*
* This is the workhorse for coroutines: it implements both yield and
* resume.
*
* It is important that both be implemented in the same callback: the
* detection of the impossibility to suspend due to a busy C-stack relies
* on the precise position of a local variable in the stack. We do not
* want the compiler to play tricks on us, either by moving things around
* or inlining.
*
*----------------------------------------------------------------------
*/
int
TclNRCoroutineActivateCallback(
ClientData data[],
Tcl_Interp *interp,
int result)
{
CoroutineData *corPtr = data[0];
int type = PTR2INT(data[1]);
int numLevels, unused;
int *stackLevel = &unused;
if (!corPtr->stackLevel) {
/*
* -- Coroutine is suspended --
* Push the callback to restore the caller's context on yield or
* return.
*/
TclNRAddCallback(interp, NRCoroutineCallerCallback, corPtr,
NULL, NULL, NULL);
/*
* Record the stackLevel at which the resume is happening, then swap
* the interp's environment to make it suitable to run this coroutine.
*/
corPtr->stackLevel = stackLevel;
numLevels = corPtr->auxNumLevels;
corPtr->auxNumLevels = iPtr->numLevels;
SAVE_CONTEXT(corPtr->caller);
corPtr->callerEEPtr = iPtr->execEnvPtr;
RESTORE_CONTEXT(corPtr->running);
iPtr->execEnvPtr = corPtr->eePtr;
iPtr->numLevels += numLevels;
} else {
/*
* Coroutine is active: yield
*/
if (corPtr->stackLevel != stackLevel) {
Tcl_SetObjResult(interp, Tcl_NewStringObj(
"cannot yield: C stack busy", -1));
Tcl_SetErrorCode(interp, "TCL", "COROUTINE", "CANT_YIELD",
NULL);
return TCL_ERROR;
}
if (type == CORO_ACTIVATE_YIELD) {
corPtr->nargs = COROUTINE_ARGUMENTS_SINGLE_OPTIONAL;
} else if (type == CORO_ACTIVATE_YIELDM) {
corPtr->nargs = COROUTINE_ARGUMENTS_ARBITRARY;
} else {
Tcl_Panic("Yield received an option which is not implemented");
}
corPtr->stackLevel = NULL;
numLevels = iPtr->numLevels;
iPtr->numLevels = corPtr->auxNumLevels;
corPtr->auxNumLevels = numLevels - corPtr->auxNumLevels;
iPtr->execEnvPtr = corPtr->callerEEPtr;
}
return TCL_OK;
}
/*
*----------------------------------------------------------------------
*
* TclNREvalList --
*
* Callback to invoke command as list, used in order to delayed
* processing of canonical list command in sane environment.
*
*----------------------------------------------------------------------
*/
static int
TclNREvalList(
ClientData data[],
Tcl_Interp *interp,
int result)
{
int objc;
Tcl_Obj **objv;
Tcl_Obj *listPtr = data[0];
Tcl_IncrRefCount(listPtr);
TclMarkTailcall(interp);
TclNRAddCallback(interp, TclNRReleaseValues, listPtr, NULL, NULL,NULL);
TclListObjGetElements(NULL, listPtr, &objc, &objv);
return TclNREvalObjv(interp, objc, objv, 0, NULL);
}
/*
*----------------------------------------------------------------------
*
* CoroTypeObjCmd --
*
* Implementation of [::tcl::unsupported::corotype] command.
*
*----------------------------------------------------------------------
*/
static int
CoroTypeObjCmd(
ClientData clientData,
Tcl_Interp *interp,
int objc,
Tcl_Obj *const objv[])
{
Command *cmdPtr;
CoroutineData *corPtr;
if (objc != 2) {
Tcl_WrongNumArgs(interp, 1, objv, "coroName");
return TCL_ERROR;
}
/*
* Look up the coroutine.
*/
cmdPtr = (Command *) Tcl_GetCommandFromObj(interp, objv[1]);
if ((!cmdPtr) || (cmdPtr->nreProc != TclNRInterpCoroutine)) {
Tcl_SetObjResult(interp, Tcl_NewStringObj(
"can only get coroutine type of a coroutine", -1));
Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "COROUTINE",
TclGetString(objv[1]), NULL);
return TCL_ERROR;
}
/*
* An active coroutine is "active". Can't tell what it might do in the
* future.
*/
corPtr = cmdPtr->objClientData;
if (!COR_IS_SUSPENDED(corPtr)) {
Tcl_SetObjResult(interp, Tcl_NewStringObj("active", -1));
return TCL_OK;
}
/*
* Inactive coroutines are classified by the (effective) command used to
* suspend them, which matters when you're injecting a probe.
*/
switch (corPtr->nargs) {
case COROUTINE_ARGUMENTS_SINGLE_OPTIONAL:
Tcl_SetObjResult(interp, Tcl_NewStringObj("yield", -1));
return TCL_OK;
case COROUTINE_ARGUMENTS_ARBITRARY:
Tcl_SetObjResult(interp, Tcl_NewStringObj("yieldto", -1));
return TCL_OK;
default:
Tcl_SetObjResult(interp, Tcl_NewStringObj(
"unknown coroutine type", -1));
Tcl_SetErrorCode(interp, "TCL", "COROUTINE", "BAD_TYPE", NULL);
return TCL_ERROR;
}
}
/*
*----------------------------------------------------------------------
*
* NRCoroInjectObjCmd --
*
* Implementation of [::tcl::unsupported::inject] command.
*
*----------------------------------------------------------------------
*/
static int
NRCoroInjectObjCmd(
ClientData clientData,
Tcl_Interp *interp,
int objc,
Tcl_Obj *const objv[])
{
Command *cmdPtr;
CoroutineData *corPtr;
ExecEnv *savedEEPtr = iPtr->execEnvPtr;
/*
* Usage more or less like tailcall:
* inject coroName cmd ?arg1 arg2 ...?
*/
if (objc < 3) {
Tcl_WrongNumArgs(interp, 1, objv, "coroName cmd ?arg1 arg2 ...?");
return TCL_ERROR;
}
cmdPtr = (Command *) Tcl_GetCommandFromObj(interp, objv[1]);
if ((!cmdPtr) || (cmdPtr->nreProc != TclNRInterpCoroutine)) {
Tcl_SetObjResult(interp, Tcl_NewStringObj(
"can only inject a command into a coroutine", -1));
Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "COROUTINE",
TclGetString(objv[1]), NULL);
return TCL_ERROR;
}
corPtr = cmdPtr->objClientData;
if (!COR_IS_SUSPENDED(corPtr)) {
Tcl_SetObjResult(interp, Tcl_NewStringObj(
"can only inject a command into a suspended coroutine", -1));
Tcl_SetErrorCode(interp, "TCL", "COROUTINE", "ACTIVE", NULL);
return TCL_ERROR;
}
/*
* Add the callback to the coro's execEnv, so that it is the first thing
* to happen when the coro is resumed.
*/
iPtr->execEnvPtr = corPtr->eePtr;
TclNRAddCallback(interp, TclNREvalList, Tcl_NewListObj(objc-2, objv+2),
NULL, NULL, NULL);
iPtr->execEnvPtr = savedEEPtr;
return TCL_OK;
}
int
TclNRInterpCoroutine(
ClientData clientData,
Tcl_Interp *interp, /* Current interpreter. */
int objc, /* Number of arguments. */
Tcl_Obj *const objv[]) /* Argument objects. */
{
CoroutineData *corPtr = clientData;
if (!COR_IS_SUSPENDED(corPtr)) {
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
"coroutine \"%s\" is already running",
Tcl_GetString(objv[0])));
Tcl_SetErrorCode(interp, "TCL", "COROUTINE", "BUSY", NULL);
return TCL_ERROR;
}
/*
* Parse all the arguments to work out what to feed as the result of the
* [yield]. TRICKY POINT: objc==0 happens here! It occurs when a coroutine
* is deleted!
*/
switch (corPtr->nargs) {
case COROUTINE_ARGUMENTS_SINGLE_OPTIONAL:
if (objc == 2) {
Tcl_SetObjResult(interp, objv[1]);
} else if (objc > 2) {
Tcl_WrongNumArgs(interp, 1, objv, "?arg?");
return TCL_ERROR;
}
break;
default:
if (corPtr->nargs != objc-1) {
Tcl_SetObjResult(interp,
Tcl_NewStringObj("wrong coro nargs; how did we get here? "
"not implemented!", -1));
Tcl_SetErrorCode(interp, "TCL", "WRONGARGS", NULL);
return TCL_ERROR;
}
/* fallthrough */
case COROUTINE_ARGUMENTS_ARBITRARY:
if (objc > 1) {
Tcl_SetObjResult(interp, Tcl_NewListObj(objc-1, objv+1));
}
break;
}
TclNRAddCallback(interp, TclNRCoroutineActivateCallback, corPtr,
NULL, NULL, NULL);
return TCL_OK;
}
/*
*----------------------------------------------------------------------
*
* TclNRCoroutineObjCmd --
*
* Implementation of [coroutine] command; see documentation for
* description of what this does.
*
*----------------------------------------------------------------------
*/
int
TclNRCoroutineObjCmd(
ClientData dummy, /* Not used. */
Tcl_Interp *interp, /* Current interpreter. */
int objc, /* Number of arguments. */
Tcl_Obj *const objv[]) /* Argument objects. */
{
Command *cmdPtr;
CoroutineData *corPtr;
const char *procName, *simpleName;
Namespace *nsPtr, *altNsPtr, *cxtNsPtr,
*inNsPtr = (Namespace *)TclGetCurrentNamespace(interp);
Namespace *lookupNsPtr = iPtr->varFramePtr->nsPtr;
if (objc < 3) {
Tcl_WrongNumArgs(interp, 1, objv, "name cmd ?arg ...?");
return TCL_ERROR;
}
procName = TclGetString(objv[1]);
TclGetNamespaceForQualName(interp, procName, inNsPtr, 0,
&nsPtr, &altNsPtr, &cxtNsPtr, &simpleName);
if (nsPtr == NULL) {
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
"can't create procedure \"%s\": unknown namespace",
procName));
Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "NAMESPACE", NULL);
return TCL_ERROR;
}
if (simpleName == NULL) {
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
"can't create procedure \"%s\": bad procedure name",
procName));
Tcl_SetErrorCode(interp, "TCL", "VALUE", "COMMAND", procName, NULL);
return TCL_ERROR;
}
/*
* We ARE creating the coroutine command: allocate the corresponding
* struct and create the corresponding command.
*/
corPtr = ckalloc(sizeof(CoroutineData));
cmdPtr = (Command *) TclNRCreateCommandInNs(interp, simpleName,
(Tcl_Namespace *)nsPtr, /*objProc*/ NULL, TclNRInterpCoroutine,
corPtr, DeleteCoroutine);
corPtr->cmdPtr = cmdPtr;
cmdPtr->refCount++;
/*
* #280.
* Provide the new coroutine with its own copy of the lineLABCPtr
* hashtable for literal command arguments in bytecode. Note that that
* CFWordBC chains are not duplicated, only the entrypoints to them. This
* means that in the presence of coroutines each chain is potentially a
* tree. Like the chain -> tree conversion of the CmdFrame stack.
*/
{
Tcl_HashSearch hSearch;
Tcl_HashEntry *hePtr;
corPtr->lineLABCPtr = ckalloc(sizeof(Tcl_HashTable));
Tcl_InitHashTable(corPtr->lineLABCPtr, TCL_ONE_WORD_KEYS);
for (hePtr = Tcl_FirstHashEntry(iPtr->lineLABCPtr,&hSearch);
hePtr; hePtr = Tcl_NextHashEntry(&hSearch)) {
int isNew;
Tcl_HashEntry *newPtr =
Tcl_CreateHashEntry(corPtr->lineLABCPtr,
Tcl_GetHashKey(iPtr->lineLABCPtr, hePtr),
&isNew);
Tcl_SetHashValue(newPtr, Tcl_GetHashValue(hePtr));
}
}
/*
* Create the base context.
*/
corPtr->running.framePtr = iPtr->rootFramePtr;
corPtr->running.varFramePtr = iPtr->rootFramePtr;
corPtr->running.cmdFramePtr = NULL;
corPtr->running.lineLABCPtr = corPtr->lineLABCPtr;
corPtr->stackLevel = NULL;
corPtr->auxNumLevels = 0;
/*
* Create the coro's execEnv, switch to it to push the exit and coro
* command callbacks, then switch back.
*/
corPtr->eePtr = TclCreateExecEnv(interp, CORO_STACK_INITIAL_SIZE);
corPtr->callerEEPtr = iPtr->execEnvPtr;
corPtr->eePtr->corPtr = corPtr;
SAVE_CONTEXT(corPtr->caller);
corPtr->callerEEPtr = iPtr->execEnvPtr;
RESTORE_CONTEXT(corPtr->running);
iPtr->execEnvPtr = corPtr->eePtr;
TclNRAddCallback(interp, NRCoroutineExitCallback, corPtr,
NULL, NULL, NULL);
/*
* Ensure that the command is looked up in the correct namespace.
*/
iPtr->lookupNsPtr = lookupNsPtr;
Tcl_NREvalObj(interp, Tcl_NewListObj(objc - 2, objv + 2), 0);
iPtr->numLevels--;
SAVE_CONTEXT(corPtr->running);
RESTORE_CONTEXT(corPtr->caller);
iPtr->execEnvPtr = corPtr->callerEEPtr;
/*
* Now just resume the coroutine.
*/
TclNRAddCallback(interp, TclNRCoroutineActivateCallback, corPtr,
NULL, NULL, NULL);
return TCL_OK;
}
/*
* This is used in the [info] ensemble
*/
int
TclInfoCoroutineCmd(
ClientData dummy,
Tcl_Interp *interp,
int objc,
Tcl_Obj *const objv[])
{
CoroutineData *corPtr = iPtr->execEnvPtr->corPtr;
if (objc != 1) {
Tcl_WrongNumArgs(interp, 1, objv, NULL);
return TCL_ERROR;
}
if (corPtr && !(corPtr->cmdPtr->flags & CMD_IS_DELETED)) {
Tcl_Obj *namePtr;
TclNewObj(namePtr);
Tcl_GetCommandFullName(interp, (Tcl_Command) corPtr->cmdPtr, namePtr);
Tcl_SetObjResult(interp, namePtr);
}
return TCL_OK;
}
#undef iPtr
/*
* Local Variables:
* mode: c
* c-basic-offset: 4
* fill-column: 78
* tab-width: 8
* indent-tabs-mode: nil
* End:
*/