diff options
Diffstat (limited to 'generic/tclProc.c')
-rw-r--r-- | generic/tclProc.c | 3089 |
1 files changed, 1998 insertions, 1091 deletions
diff --git a/generic/tclProc.c b/generic/tclProc.c index abfd152..43e88c6 100644 --- a/generic/tclProc.c +++ b/generic/tclProc.c @@ -1,15 +1,16 @@ /* * tclProc.c -- * - * This file contains routines that implement Tcl procedures, - * including the "proc" and "uplevel" commands. + * This file contains routines that implement Tcl procedures, including + * the "proc" and "uplevel" commands. * * Copyright (c) 1987-1993 The Regents of the University of California. * Copyright (c) 1994-1998 Sun Microsystems, Inc. + * Copyright (c) 2004-2006 Miguel Sofer * Copyright (c) 2007 Daniel A. Steffen <das@users.sourceforge.net> * - * See the file "license.terms" for information on usage and redistribution - * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + * See the file "license.terms" for information on usage and redistribution of + * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" @@ -19,19 +20,30 @@ * Prototypes for static functions in this file */ -static void ProcBodyDup _ANSI_ARGS_((Tcl_Obj *srcPtr, Tcl_Obj *dupPtr)); -static void ProcBodyFree _ANSI_ARGS_((Tcl_Obj *objPtr)); -static int ProcBodySetFromAny _ANSI_ARGS_((Tcl_Interp *interp, - Tcl_Obj *objPtr)); -static void ProcBodyUpdateString _ANSI_ARGS_((Tcl_Obj *objPtr)); -static int ProcCompileProc _ANSI_ARGS_((Tcl_Interp *interp, - Proc *procPtr, Tcl_Obj *bodyPtr, Namespace *nsPtr, - CONST char *description, CONST char *procName, - Proc **procPtrPtr)); -static int ProcessProcResultCode _ANSI_ARGS_((Tcl_Interp *interp, - char *procName, int nameLen, int returnCode)); -static int TclCompileNoOp _ANSI_ARGS_((Tcl_Interp *interp, - Tcl_Parse *parsePtr, struct CompileEnv *envPtr)); +static void DupLambdaInternalRep(Tcl_Obj *objPtr, + Tcl_Obj *copyPtr); +static void FreeLambdaInternalRep(Tcl_Obj *objPtr); +static int InitArgsAndLocals(Tcl_Interp *interp, + Tcl_Obj *procNameObj, int skip); +static void InitResolvedLocals(Tcl_Interp *interp, + ByteCode *codePtr, Var *defPtr, + Namespace *nsPtr); +static void InitLocalCache(Proc *procPtr); +static int PushProcCallFrame(ClientData clientData, + register Tcl_Interp *interp, int objc, + Tcl_Obj *CONST objv[], int isLambda); +static void ProcBodyDup(Tcl_Obj *srcPtr, Tcl_Obj *dupPtr); +static void ProcBodyFree(Tcl_Obj *objPtr); +static int ProcWrongNumArgs(Tcl_Interp *interp, int skip); +static void MakeProcError(Tcl_Interp *interp, + Tcl_Obj *procNameObj); +static void MakeLambdaError(Tcl_Interp *interp, + Tcl_Obj *procNameObj); +static int SetLambdaFromAny(Tcl_Interp *interp, Tcl_Obj *objPtr); +static int ProcCompileProc(Tcl_Interp *interp, Proc *procPtr, + Tcl_Obj *bodyPtr, Namespace *nsPtr, + CONST char *description, CONST char *procName, + Proc **procPtrPtr); /* * The ProcBodyObjType type @@ -39,10 +51,44 @@ static int TclCompileNoOp _ANSI_ARGS_((Tcl_Interp *interp, Tcl_ObjType tclProcBodyType = { "procbody", /* name for this type */ - ProcBodyFree, /* FreeInternalRep procedure */ - ProcBodyDup, /* DupInternalRep procedure */ - ProcBodyUpdateString, /* UpdateString procedure */ - ProcBodySetFromAny /* SetFromAny procedure */ + ProcBodyFree, /* FreeInternalRep function */ + ProcBodyDup, /* DupInternalRep function */ + NULL, /* UpdateString function; Tcl_GetString and + * Tcl_GetStringFromObj should panic + * instead. */ + NULL /* SetFromAny function; Tcl_ConvertToType + * should panic instead. */ +}; + +/* + * The [upvar]/[uplevel] level reference type. Uses the twoPtrValue field, + * encoding the type of level reference in ptr1 and the actual parsed out + * offset in ptr2. + * + * Uses the default behaviour throughout, and never disposes of the string + * rep; it's just a cache type. + */ + +static Tcl_ObjType levelReferenceType = { + "levelReference", + NULL, NULL, NULL, NULL +}; + +/* + * The type of lambdas. Note that every lambda will *always* have a string + * representation. + * + * Internally, ptr1 is a pointer to a Proc instance that is not bound to a + * command name, and ptr2 is a pointer to the namespace that the Proc instance + * will execute within. + */ + +static Tcl_ObjType lambdaType = { + "lambdaExpr", /* name */ + FreeLambdaInternalRep, /* freeIntRepProc */ + DupLambdaInternalRep, /* dupIntRepProc */ + NULL, /* updateStringProc */ + SetLambdaFromAny /* setFromAnyProc */ }; /* @@ -50,7 +96,7 @@ Tcl_ObjType tclProcBodyType = { * * Tcl_ProcObjCmd -- * - * This object-based procedure is invoked to process the "proc" Tcl + * This object-based function is invoked to process the "proc" Tcl * command. See the user documentation for details on what it does. * * Results: @@ -64,11 +110,11 @@ Tcl_ObjType tclProcBodyType = { /* ARGSUSED */ int -Tcl_ProcObjCmd(dummy, interp, objc, objv) - ClientData dummy; /* Not used. */ - Tcl_Interp *interp; /* Current interpreter. */ - int objc; /* Number of arguments. */ - Tcl_Obj *CONST objv[]; /* Argument objects. */ +Tcl_ProcObjCmd( + ClientData dummy, /* Not used. */ + Tcl_Interp *interp, /* Current interpreter. */ + int objc, /* Number of arguments. */ + Tcl_Obj *CONST objv[]) /* Argument objects. */ { register Interp *iPtr = (Interp *) interp; Proc *procPtr; @@ -84,47 +130,48 @@ Tcl_ProcObjCmd(dummy, interp, objc, objv) } /* - * Determine the namespace where the procedure should reside. Unless - * the command name includes namespace qualifiers, this will be the - * current namespace. + * Determine the namespace where the procedure should reside. Unless the + * command name includes namespace qualifiers, this will be the current + * namespace. */ fullName = TclGetString(objv[1]); - TclGetNamespaceForQualName(interp, fullName, (Namespace *) NULL, - 0, &nsPtr, &altNsPtr, &cxtNsPtr, &procName); + TclGetNamespaceForQualName(interp, fullName, NULL, 0, + &nsPtr, &altNsPtr, &cxtNsPtr, &procName); if (nsPtr == NULL) { - Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), - "can't create procedure \"", fullName, - "\": unknown namespace", (char *) NULL); - return TCL_ERROR; + Tcl_AppendResult(interp, "can't create procedure \"", fullName, + "\": unknown namespace", NULL); + return TCL_ERROR; } if (procName == NULL) { - Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), - "can't create procedure \"", fullName, - "\": bad procedure name", (char *) NULL); - return TCL_ERROR; + Tcl_AppendResult(interp, "can't create procedure \"", fullName, + "\": bad procedure name", NULL); + return TCL_ERROR; } if ((nsPtr != iPtr->globalNsPtr) && (procName != NULL) && (procName[0] == ':')) { - Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), - "can't create procedure \"", procName, + Tcl_AppendResult(interp, "can't create procedure \"", procName, "\" in non-global namespace with name starting with \":\"", - (char *) NULL); - return TCL_ERROR; + NULL); + return TCL_ERROR; } /* - * Create the data structure to represent the procedure. + * Create the data structure to represent the procedure. */ + if (TclCreateProc(interp, nsPtr, procName, objv[2], objv[3], - &procPtr) != TCL_OK) { - return TCL_ERROR; + &procPtr) != TCL_OK) { + Tcl_AddErrorInfo(interp, "\n (creating proc \""); + Tcl_AddErrorInfo(interp, procName); + Tcl_AddErrorInfo(interp, "\")"); + return TCL_ERROR; } /* - * Now create a command for the procedure. This will initially be in - * the current namespace unless the procedure's name included namespace + * Now create a command for the procedure. This will initially be in the + * current namespace unless the procedure's name included namespace * qualifiers. To create the new command in the right namespace, we * generate a fully qualified name for it. */ @@ -136,12 +183,11 @@ Tcl_ProcObjCmd(dummy, interp, objc, objv) } Tcl_DStringAppend(&ds, procName, -1); - Tcl_CreateCommand(interp, Tcl_DStringValue(&ds), TclProcInterpProc, - (ClientData) procPtr, TclProcDeleteProc); cmd = Tcl_CreateObjCommand(interp, Tcl_DStringValue(&ds), TclObjInterpProc, (ClientData) procPtr, TclProcDeleteProc); Tcl_DStringFree(&ds); + /* * Now initialize the new procedure's cmdPtr field. This will be used * later when the procedure is called to determine what namespace the @@ -151,113 +197,133 @@ Tcl_ProcObjCmd(dummy, interp, objc, objv) procPtr->cmdPtr = (Command *) cmd; -#ifdef TCL_TIP280 - /* TIP #280 Remember the line the procedure body is starting on. In a - * Byte code context we ask the engine to provide us with the necessary + /* + * TIP #280: Remember the line the procedure body is starting on. In a + * bytecode context we ask the engine to provide us with the necessary * information. This is for the initialization of the byte code compiler * when the body is used for the first time. + * + * This code is nearly identical to the #280 code in SetLambdaFromAny, see + * this file. The differences are the different index of the body in the + * line array of the context, and the lamdba code requires some special + * processing. Find a way to factor the common elements into a single + * function. */ if (iPtr->cmdFramePtr) { - CmdFrame context = *iPtr->cmdFramePtr; - - if (context.type == TCL_LOCATION_BC) { - TclGetSrcInfoForPc (&context); - /* May get path in context */ - } else if (context.type == TCL_LOCATION_SOURCE) { - /* context now holds another reference */ - Tcl_IncrRefCount (context.data.eval.path); - } + CmdFrame *contextPtr; - /* type == TCL_LOCATION_PREBC implies that 'line' is NULL here! We - * cannot assume that 'line' is valid here, we have to check. If the - * outer context is an eval (bc, prebc, eval) we do not save any - * information. Counting relative to the beginning of the proc body is - * more sensible than counting relative to the outer eval block. - */ + contextPtr = (CmdFrame *) TclStackAlloc(interp, sizeof(CmdFrame)); + *contextPtr = *iPtr->cmdFramePtr; - if ((context.type == TCL_LOCATION_SOURCE) && - context.line && - (context.nline >= 4) && - (context.line [3] >= 0)) { - int isNew; - Tcl_HashEntry* hePtr; - CmdFrame* cfPtr = (CmdFrame*) ckalloc (sizeof (CmdFrame)); - - cfPtr->level = -1; - cfPtr->type = context.type; - cfPtr->line = (int*) ckalloc (sizeof (int)); - cfPtr->line [0] = context.line [3]; - cfPtr->nline = 1; - cfPtr->framePtr = NULL; - cfPtr->nextPtr = NULL; - - if (context.type == TCL_LOCATION_SOURCE) { - cfPtr->data.eval.path = context.data.eval.path; - /* Transfer of reference. The reference going away (release of - * the context) is replaced by the reference in the - * constructed cmdframe */ - } else { - cfPtr->type = TCL_LOCATION_EVAL; - cfPtr->data.eval.path = NULL; - } + if (contextPtr->type == TCL_LOCATION_BC) { + /* + * Retrieve source information from the bytecode, if possible. If + * the information is retrieved successfully, context.type will be + * TCL_LOCATION_SOURCE and the reference held by + * context.data.eval.path will be counted. + */ - cfPtr->cmd.str.cmd = NULL; - cfPtr->cmd.str.len = 0; + TclGetSrcInfoForPc(contextPtr); + } else if (contextPtr->type == TCL_LOCATION_SOURCE) { + /* + * The copy into 'context' up above has created another reference + * to 'context.data.eval.path'; account for it. + */ - hePtr = Tcl_CreateHashEntry (iPtr->linePBodyPtr, (char*) procPtr, - &isNew); - if (!isNew) { - /* - * Get the old command frame and release it. See also - * TclProcCleanupProc in this file. Currently it seems as if - * only the procbodytest::proc command of the testsuite is - * able to trigger this situation. - */ + Tcl_IncrRefCount(contextPtr->data.eval.path); + } + + if (contextPtr->type == TCL_LOCATION_SOURCE) { + /* + * We can account for source location within a proc only if the + * proc body was not created by substitution. + */ + + if (contextPtr->line + && (contextPtr->nline >= 4) && (contextPtr->line[3] >= 0)) { + int isNew; + Tcl_HashEntry* hePtr; + CmdFrame *cfPtr = (CmdFrame *) ckalloc(sizeof(CmdFrame)); + + cfPtr->level = -1; + cfPtr->type = contextPtr->type; + cfPtr->line = (int *) ckalloc(sizeof(int)); + cfPtr->line[0] = contextPtr->line[3]; + cfPtr->nline = 1; + cfPtr->framePtr = NULL; + cfPtr->nextPtr = NULL; + + cfPtr->data.eval.path = contextPtr->data.eval.path; + Tcl_IncrRefCount(cfPtr->data.eval.path); + + cfPtr->cmd.str.cmd = NULL; + cfPtr->cmd.str.len = 0; + + hePtr = Tcl_CreateHashEntry(iPtr->linePBodyPtr, (char *) procPtr, &isNew); + if (!isNew) { + /* + * Get the old command frame and release it. See also + * TclProcCleanupProc in this file. Currently it seems as + * if only the procbodytest::proc command of the testsuite + * is able to trigger this situation. + */ - CmdFrame* cfOldPtr = (CmdFrame *) Tcl_GetHashValue(hePtr); + CmdFrame* cfOldPtr = (CmdFrame *) Tcl_GetHashValue(hePtr); - if (cfOldPtr->type == TCL_LOCATION_SOURCE) { - Tcl_DecrRefCount(cfOldPtr->data.eval.path); - cfOldPtr->data.eval.path = NULL; + if (cfOldPtr->type == TCL_LOCATION_SOURCE) { + Tcl_DecrRefCount(cfOldPtr->data.eval.path); + cfOldPtr->data.eval.path = NULL; + } + ckfree((char *) cfOldPtr->line); + cfOldPtr->line = NULL; + ckfree((char *) cfOldPtr); } - ckfree((char *) cfOldPtr->line); - cfOldPtr->line = NULL; - ckfree((char *) cfOldPtr); + Tcl_SetHashValue(hePtr, cfPtr); } - Tcl_SetHashValue (hePtr, cfPtr); + + /* + * 'contextPtr' is going out of scope; account for the reference that + * it's holding to the path name. + */ + + Tcl_DecrRefCount(contextPtr->data.eval.path); + contextPtr->data.eval.path = NULL; } + TclStackFree(interp, contextPtr); } -#endif /* - * Optimize for noop procs: if the body is not precompiled (like a TclPro + * Optimize for no-op procs: if the body is not precompiled (like a TclPro * procbody), and the argument list is just "args" and the body is empty, - * define a compileProc to compile a noop. + * define a compileProc to compile a no-op. * * Notes: - * - cannot be done for any argument list without having different - * compiled/not-compiled behaviour in the "wrong argument #" case, - * or making this code much more complicated. In any case, it doesn't - * seem to make a lot of sense to verify the number of arguments we - * are about to ignore ... - * - could be enhanced to handle also non-empty bodies that contain - * only comments; however, parsing the body will slow down the - * compilation of all procs whose argument list is just _args_ */ + * - cannot be done for any argument list without having different + * compiled/not-compiled behaviour in the "wrong argument #" case, or + * making this code much more complicated. In any case, it doesn't + * seem to make a lot of sense to verify the number of arguments we + * are about to ignore ... + * - could be enhanced to handle also non-empty bodies that contain only + * comments; however, parsing the body will slow down the compilation + * of all procs whose argument list is just _args_ + */ if (objv[3]->typePtr == &tclProcBodyType) { goto done; } - procArgs = Tcl_GetString(objv[2]); + procArgs = TclGetString(objv[2]); while (*procArgs == ' ') { procArgs++; } if ((procArgs[0] == 'a') && (strncmp(procArgs, "args", 4) == 0)) { + int numBytes; + procArgs +=4; - while(*procArgs != '\0') { + while (*procArgs != '\0') { if (*procArgs != ' ') { goto done; } @@ -268,12 +334,9 @@ Tcl_ProcObjCmd(dummy, interp, objc, objv) * The argument list is just "args"; check the body */ - procBody = Tcl_GetString(objv[3]); - while (*procBody != '\0') { - if (!isspace(UCHAR(*procBody))) { - goto done; - } - procBody++; + procBody = Tcl_GetStringFromObj(objv[3], &numBytes); + if (TclParseAllWhiteSpace(procBody, numBytes) < numBytes) { + goto done; } /* @@ -283,7 +346,7 @@ Tcl_ProcObjCmd(dummy, interp, objc, objv) ((Command *) cmd)->compileProc = TclCompileNoOp; } - done: + done: return TCL_OK; } @@ -292,34 +355,35 @@ Tcl_ProcObjCmd(dummy, interp, objc, objv) * * TclCreateProc -- * - * Creates the data associated with a Tcl procedure definition. - * This procedure knows how to handle two types of body objects: - * strings and procbody. Strings are the traditional (and common) value - * for bodies, procbody are values created by extensions that have - * loaded a previously compiled script. + * Creates the data associated with a Tcl procedure definition. This + * function knows how to handle two types of body objects: strings and + * procbody. Strings are the traditional (and common) value for bodies, + * procbody are values created by extensions that have loaded a + * previously compiled script. * * Results: - * Returns TCL_OK on success, along with a pointer to a Tcl - * procedure definition in procPtrPtr. This definition should - * be freed by calling TclCleanupProc() when it is no longer - * needed. Returns TCL_ERROR if anything goes wrong. + * Returns TCL_OK on success, along with a pointer to a Tcl procedure + * definition in procPtrPtr where the cmdPtr field is not initialised. + * This definition should be freed by calling TclProcCleanupProc() when + * it is no longer needed. Returns TCL_ERROR if anything goes wrong. * * Side effects: - * If anything goes wrong, this procedure returns an error - * message in the interpreter. + * If anything goes wrong, this function returns an error message in the + * interpreter. * *---------------------------------------------------------------------- */ + int -TclCreateProc(interp, nsPtr, procName, argsPtr, bodyPtr, procPtrPtr) - Tcl_Interp *interp; /* interpreter containing proc */ - Namespace *nsPtr; /* namespace containing this proc */ - CONST char *procName; /* unqualified name of this proc */ - Tcl_Obj *argsPtr; /* description of arguments */ - Tcl_Obj *bodyPtr; /* command body */ - Proc **procPtrPtr; /* returns: pointer to proc data */ +TclCreateProc( + Tcl_Interp *interp, /* Interpreter containing proc. */ + Namespace *nsPtr, /* Namespace containing this proc. */ + CONST char *procName, /* Unqualified name of this proc. */ + Tcl_Obj *argsPtr, /* Description of arguments. */ + Tcl_Obj *bodyPtr, /* Command body. */ + Proc **procPtrPtr) /* Returns: pointer to proc data. */ { - Interp *iPtr = (Interp*)interp; + Interp *iPtr = (Interp *) interp; CONST char **argArray = NULL; register Proc *procPtr; @@ -330,46 +394,44 @@ TclCreateProc(interp, nsPtr, procName, argsPtr, bodyPtr, procPtrPtr) int precompiled = 0; if (bodyPtr->typePtr == &tclProcBodyType) { - /* - * Because the body is a TclProProcBody, the actual body is already - * compiled, and it is not shared with anyone else, so it's OK not to - * unshare it (as a matter of fact, it is bad to unshare it, because - * there may be no source code). - * - * We don't create and initialize a Proc structure for the procedure; - * rather, we use what is in the body object. Note that - * we initialize its cmdPtr field below after we've created the command - * for the procedure. We increment the ref count of the Proc struct - * since the command (soon to be created) will be holding a reference - * to it. - */ - - procPtr = (Proc *) bodyPtr->internalRep.otherValuePtr; - procPtr->iPtr = iPtr; - procPtr->refCount++; - precompiled = 1; + /* + * Because the body is a TclProProcBody, the actual body is already + * compiled, and it is not shared with anyone else, so it's OK not to + * unshare it (as a matter of fact, it is bad to unshare it, because + * there may be no source code). + * + * We don't create and initialize a Proc structure for the procedure; + * rather, we use what is in the body object. We increment the ref + * count of the Proc struct since the command (soon to be created) + * will be holding a reference to it. + */ + + procPtr = bodyPtr->internalRep.otherValuePtr; + procPtr->iPtr = iPtr; + procPtr->refCount++; + precompiled = 1; } else { - /* - * If the procedure's body object is shared because its string value is - * identical to, e.g., the body of another procedure, we must create a - * private copy for this procedure to use. Such sharing of procedure - * bodies is rare but can cause problems. A procedure body is compiled - * in a context that includes the number of compiler-allocated "slots" - * for local variables. Each formal parameter is given a local variable - * slot (the "procPtr->numCompiledLocals = numArgs" assignment - * below). This means that the same code can not be shared by two - * procedures that have a different number of arguments, even if their - * bodies are identical. Note that we don't use Tcl_DuplicateObj since - * we would not want any bytecode internal representation. - */ - - if (Tcl_IsShared(bodyPtr)) { -#ifdef TCL_TIP280 + /* + * If the procedure's body object is shared because its string value + * is identical to, e.g., the body of another procedure, we must + * create a private copy for this procedure to use. Such sharing of + * procedure bodies is rare but can cause problems. A procedure body + * is compiled in a context that includes the number of "slots" + * allocated by the compiler for local variables. There is a local + * variable slot for each formal parameter (the + * "procPtr->numCompiledLocals = numArgs" assignment below). This + * means that the same code can not be shared by two procedures that + * have a different number of arguments, even if their bodies are + * identical. Note that we don't use Tcl_DuplicateObj since we would + * not want any bytecode internal representation. + */ + + if (Tcl_IsShared(bodyPtr)) { Tcl_Obj* sharedBodyPtr = bodyPtr; -#endif - bytes = Tcl_GetStringFromObj(bodyPtr, &length); - bodyPtr = Tcl_NewStringObj(bytes, length); -#ifdef TCL_TIP280 + + bytes = TclGetStringFromObj(bodyPtr, &length); + bodyPtr = Tcl_NewStringObj(bytes, length); + /* * TIP #280. * Ensure that the continuation line data for the original body is @@ -377,121 +439,111 @@ TclCreateProc(interp, nsPtr, procName, argsPtr, bodyPtr, procPtrPtr) */ TclContinuationsCopy (bodyPtr, sharedBodyPtr); -#endif - } + } - /* - * Create and initialize a Proc structure for the procedure. Note that - * we initialize its cmdPtr field below after we've created the command - * for the procedure. We increment the ref count of the procedure's - * body object since there will be a reference to it in the Proc - * structure. - */ + /* + * Create and initialize a Proc structure for the procedure. We + * increment the ref count of the procedure's body object since there + * will be a reference to it in the Proc structure. + */ - Tcl_IncrRefCount(bodyPtr); + Tcl_IncrRefCount(bodyPtr); - procPtr = (Proc *) ckalloc(sizeof(Proc)); - procPtr->iPtr = iPtr; - procPtr->refCount = 1; - procPtr->bodyPtr = bodyPtr; - procPtr->numArgs = 0; /* actual argument count is set below. */ - procPtr->numCompiledLocals = 0; - procPtr->firstLocalPtr = NULL; - procPtr->lastLocalPtr = NULL; + procPtr = (Proc *) ckalloc(sizeof(Proc)); + procPtr->iPtr = iPtr; + procPtr->refCount = 1; + procPtr->bodyPtr = bodyPtr; + procPtr->numArgs = 0; /* Actual argument count is set below. */ + procPtr->numCompiledLocals = 0; + procPtr->firstLocalPtr = NULL; + procPtr->lastLocalPtr = NULL; } /* - * Break up the argument list into argument specifiers, then process - * each argument specifier. - * If the body is precompiled, processing is limited to checking that - * the the parsed argument is consistent with the one stored in the - * Proc. - * THIS FAILS IF THE ARG LIST OBJECT'S STRING REP CONTAINS NULLS. + * Break up the argument list into argument specifiers, then process each + * argument specifier. If the body is precompiled, processing is limited + * to checking that the parsed argument is consistent with the one stored + * in the Proc. + * + * THIS FAILS IF THE ARG LIST OBJECT'S STRING REP CONTAINS NULS. */ - args = Tcl_GetStringFromObj(argsPtr, &length); + args = TclGetStringFromObj(argsPtr, &length); result = Tcl_SplitList(interp, args, &numArgs, &argArray); if (result != TCL_OK) { - goto procError; + goto procError; } if (precompiled) { - if (numArgs > procPtr->numArgs) { - char buf[64 + TCL_INTEGER_SPACE + TCL_INTEGER_SPACE]; - sprintf(buf, "\": arg list contains %d entries, precompiled header expects %d", - numArgs, procPtr->numArgs); - Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), - "procedure \"", procName, - buf, (char *) NULL); - goto procError; - } - localPtr = procPtr->firstLocalPtr; + if (numArgs > procPtr->numArgs) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "procedure \"%s\": arg list contains %d entries, " + "precompiled header expects %d", procName, numArgs, + procPtr->numArgs)); + goto procError; + } + localPtr = procPtr->firstLocalPtr; } else { - procPtr->numArgs = numArgs; - procPtr->numCompiledLocals = numArgs; - } - for (i = 0; i < numArgs; i++) { - int fieldCount, nameLength, valueLength; - CONST char **fieldValues; - - /* - * Now divide the specifier up into name and default. - */ - - result = Tcl_SplitList(interp, argArray[i], &fieldCount, - &fieldValues); - if (result != TCL_OK) { - goto procError; - } - if (fieldCount > 2) { - ckfree((char *) fieldValues); - Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), - "too many fields in argument specifier \"", - argArray[i], "\"", (char *) NULL); - goto procError; - } - if ((fieldCount == 0) || (*fieldValues[0] == 0)) { - ckfree((char *) fieldValues); - Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), - "procedure \"", procName, - "\" has argument with no name", (char *) NULL); - goto procError; - } - - nameLength = strlen(fieldValues[0]); - if (fieldCount == 2) { - valueLength = strlen(fieldValues[1]); - } else { - valueLength = 0; - } - - /* - * Check that the formal parameter name is a scalar. - */ - - p = fieldValues[0]; - while (*p != '\0') { - if (*p == '(') { - CONST char *q = p; - do { + procPtr->numArgs = numArgs; + procPtr->numCompiledLocals = numArgs; + } + + for (i = 0; i < numArgs; i++) { + int fieldCount, nameLength, valueLength; + CONST char **fieldValues; + + /* + * Now divide the specifier up into name and default. + */ + + result = Tcl_SplitList(interp, argArray[i], &fieldCount, + &fieldValues); + if (result != TCL_OK) { + goto procError; + } + if (fieldCount > 2) { + ckfree((char *) fieldValues); + Tcl_AppendResult(interp, + "too many fields in argument specifier \"", + argArray[i], "\"", NULL); + goto procError; + } + if ((fieldCount == 0) || (*fieldValues[0] == 0)) { + ckfree((char *) fieldValues); + Tcl_AppendResult(interp, "argument with no name", NULL); + goto procError; + } + + nameLength = strlen(fieldValues[0]); + if (fieldCount == 2) { + valueLength = strlen(fieldValues[1]); + } else { + valueLength = 0; + } + + /* + * Check that the formal parameter name is a scalar. + */ + + p = fieldValues[0]; + while (*p != '\0') { + if (*p == '(') { + CONST char *q = p; + do { q++; } while (*q != '\0'); q--; - if (*q == ')') { /* we have an array element */ - Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), - "procedure \"", procName, - "\" has formal parameter \"", fieldValues[0], - "\" that is an array element", - (char *) NULL); + if (*q == ')') { /* We have an array element. */ + Tcl_AppendResult(interp, "formal parameter \"", + fieldValues[0], + "\" is an array element", NULL); ckfree((char *) fieldValues); goto procError; } } else if ((*p == ':') && (*(p+1) == ':')) { - Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), - "procedure \"", procName, - "\" has formal parameter \"", fieldValues[0], - "\" that is not a simple name", - (char *) NULL); + Tcl_AppendResult(interp, "formal parameter \"", + fieldValues[0], + "\" is not a simple name", NULL); ckfree((char *) fieldValues); goto procError; } @@ -500,116 +552,117 @@ TclCreateProc(interp, nsPtr, procName, argsPtr, bodyPtr, procPtrPtr) if (precompiled) { /* - * Compare the parsed argument with the stored one. - * For the flags, we and out VAR_UNDEFINED to support bridging - * precompiled <= 8.3 code in 8.4 where this is now used as an - * optimization indicator. Yes, this is a hack. -- hobbs + * Compare the parsed argument with the stored one. Note that the + * only flag value that makes sense at this point is VAR_ARGUMENT + * (its value was kept the same as pre VarReform to simplify + * tbcload's processing of older byetcodes). + * + * The only other flag vlaue that is important to retrieve from + * precompiled procs is VAR_TEMPORARY (also unchanged). It is + * needed later when retrieving the variable names. */ if ((localPtr->nameLength != nameLength) || (strcmp(localPtr->name, fieldValues[0])) || (localPtr->frameIndex != i) - || ((localPtr->flags & ~VAR_UNDEFINED) - != (VAR_SCALAR | VAR_ARGUMENT)) - || ((localPtr->defValuePtr == NULL) - && (fieldCount == 2)) - || ((localPtr->defValuePtr != NULL) - && (fieldCount != 2))) { - char buf[80 + TCL_INTEGER_SPACE]; - sprintf(buf, "\": formal parameter %d is inconsistent with precompiled body", - i); - Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), - "procedure \"", procName, - buf, (char *) NULL); + || !(localPtr->flags & VAR_ARGUMENT) + || (localPtr->defValuePtr == NULL && fieldCount == 2) + || (localPtr->defValuePtr != NULL && fieldCount != 2)) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "procedure \"%s\": formal parameter %d is " + "inconsistent with precompiled body", procName, i)); ckfree((char *) fieldValues); goto procError; } - /* - * compare the default value if any - */ - - if (localPtr->defValuePtr != NULL) { - int tmpLength; - char *tmpPtr = Tcl_GetStringFromObj(localPtr->defValuePtr, - &tmpLength); - if ((valueLength != tmpLength) - || (strncmp(fieldValues[1], tmpPtr, - (size_t) tmpLength))) { - Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), - "procedure \"", procName, - "\": formal parameter \"", - fieldValues[0], - "\" has default value inconsistent with precompiled body", - (char *) NULL); - ckfree((char *) fieldValues); - goto procError; - } - } - - localPtr = localPtr->nextPtr; - } else { - /* - * Allocate an entry in the runtime procedure frame's array of - * local variables for the argument. - */ - - localPtr = (CompiledLocal *) ckalloc((unsigned) - (sizeof(CompiledLocal) - sizeof(localPtr->name) - + nameLength+1)); - if (procPtr->firstLocalPtr == NULL) { - procPtr->firstLocalPtr = procPtr->lastLocalPtr = localPtr; - } else { - procPtr->lastLocalPtr->nextPtr = localPtr; - procPtr->lastLocalPtr = localPtr; - } - localPtr->nextPtr = NULL; - localPtr->nameLength = nameLength; - localPtr->frameIndex = i; - localPtr->flags = VAR_SCALAR | VAR_ARGUMENT; - localPtr->resolveInfo = NULL; - - if (fieldCount == 2) { - localPtr->defValuePtr = - Tcl_NewStringObj(fieldValues[1], valueLength); - Tcl_IncrRefCount(localPtr->defValuePtr); - } else { - localPtr->defValuePtr = NULL; - } - memcpy(localPtr->name, fieldValues[0], nameLength + 1); + /* + * Compare the default value if any. + */ + + if (localPtr->defValuePtr != NULL) { + int tmpLength; + char *tmpPtr = TclGetStringFromObj(localPtr->defValuePtr, + &tmpLength); + + if ((valueLength != tmpLength) || + strncmp(fieldValues[1], tmpPtr, (size_t) tmpLength)) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "procedure \"%s\": formal parameter \"%s\" has " + "default value inconsistent with precompiled body", + procName, fieldValues[0])); + ckfree((char *) fieldValues); + goto procError; + } + } + if ((i == numArgs - 1) + && (localPtr->nameLength == 4) + && (localPtr->name[0] == 'a') + && (strcmp(localPtr->name, "args") == 0)) { + localPtr->flags |= VAR_IS_ARGS; + } + + localPtr = localPtr->nextPtr; + } else { + /* + * Allocate an entry in the runtime procedure frame's array of + * local variables for the argument. + */ + + localPtr = (CompiledLocal *) ckalloc((unsigned) + (sizeof(CompiledLocal) - sizeof(localPtr->name) + + nameLength + 1)); + if (procPtr->firstLocalPtr == NULL) { + procPtr->firstLocalPtr = procPtr->lastLocalPtr = localPtr; + } else { + procPtr->lastLocalPtr->nextPtr = localPtr; + procPtr->lastLocalPtr = localPtr; + } + localPtr->nextPtr = NULL; + localPtr->nameLength = nameLength; + localPtr->frameIndex = i; + localPtr->flags = VAR_ARGUMENT; + localPtr->resolveInfo = NULL; + + if (fieldCount == 2) { + localPtr->defValuePtr = + Tcl_NewStringObj(fieldValues[1], valueLength); + Tcl_IncrRefCount(localPtr->defValuePtr); + } else { + localPtr->defValuePtr = NULL; + } + memcpy(localPtr->name, fieldValues[0], nameLength + 1); + if ((i == numArgs - 1) + && (localPtr->nameLength == 4) + && (localPtr->name[0] == 'a') + && (strcmp(localPtr->name, "args") == 0)) { + localPtr->flags |= VAR_IS_ARGS; + } } - ckfree((char *) fieldValues); + ckfree((char *) fieldValues); } - /* - * Now initialize the new procedure's cmdPtr field. This will be used - * later when the procedure is called to determine what namespace the - * procedure will run in. This will be different than the current - * namespace if the proc was renamed into a different namespace. - */ - *procPtrPtr = procPtr; ckfree((char *) argArray); return TCL_OK; -procError: + procError: if (precompiled) { - procPtr->refCount--; + procPtr->refCount--; } else { - Tcl_DecrRefCount(bodyPtr); - while (procPtr->firstLocalPtr != NULL) { - localPtr = procPtr->firstLocalPtr; - procPtr->firstLocalPtr = localPtr->nextPtr; + Tcl_DecrRefCount(bodyPtr); + while (procPtr->firstLocalPtr != NULL) { + localPtr = procPtr->firstLocalPtr; + procPtr->firstLocalPtr = localPtr->nextPtr; - defPtr = localPtr->defValuePtr; - if (defPtr != NULL) { - Tcl_DecrRefCount(defPtr); - } + defPtr = localPtr->defValuePtr; + if (defPtr != NULL) { + Tcl_DecrRefCount(defPtr); + } - ckfree((char *) localPtr); - } - ckfree((char *) procPtr); + ckfree((char *) localPtr); + } + ckfree((char *) procPtr); } if (argArray != NULL) { ckfree((char *) argArray); @@ -622,19 +675,19 @@ procError: * * TclGetFrame -- * - * Given a description of a procedure frame, such as the first - * argument to an "uplevel" or "upvar" command, locate the - * call frame for the appropriate level of procedure. + * Given a description of a procedure frame, such as the first argument + * to an "uplevel" or "upvar" command, locate the call frame for the + * appropriate level of procedure. * * Results: - * The return value is -1 if an error occurred in finding the frame - * (in this case an error message is left in the interp's result). - * 1 is returned if string was either a number or a number preceded - * by "#" and it specified a valid frame. 0 is returned if string - * isn't one of the two things above (in this case, the lookup - * acts as if string were "1"). The variable pointed to by - * framePtrPtr is filled in with the address of the desired frame - * (unless an error occurs, in which case it isn't modified). + * The return value is -1 if an error occurred in finding the frame (in + * this case an error message is left in the interp's result). 1 is + * returned if string was either a number or a number preceded by "#" and + * it specified a valid frame. 0 is returned if string isn't one of the + * two things above (in this case, the lookup acts as if string were + * "1"). The variable pointed to by framePtrPtr is filled in with the + * address of the desired frame (unless an error occurs, in which case it + * isn't modified). * * Side effects: * None. @@ -643,11 +696,11 @@ procError: */ int -TclGetFrame(interp, string, framePtrPtr) - Tcl_Interp *interp; /* Interpreter in which to find frame. */ - CONST char *string; /* String describing frame. */ - CallFrame **framePtrPtr; /* Store pointer to frame here (or NULL - * if global frame indicated). */ +TclGetFrame( + Tcl_Interp *interp, /* Interpreter in which to find frame. */ + CONST char *name, /* String describing frame. */ + CallFrame **framePtrPtr) /* Store pointer to frame here (or NULL if + * global frame indicated). */ { register Interp *iPtr = (Interp *) interp; int curLevel, level, result; @@ -658,47 +711,166 @@ TclGetFrame(interp, string, framePtrPtr) */ result = 1; - curLevel = (iPtr->varFramePtr == NULL) ? 0 : iPtr->varFramePtr->level; - if (*string == '#') { - if (Tcl_GetInt(interp, string+1, &level) != TCL_OK) { - return -1; + curLevel = iPtr->varFramePtr->level; + if (*name== '#') { + if (Tcl_GetInt(interp, name+1, &level) != TCL_OK || level < 0) { + goto levelError; + } + } else if (isdigit(UCHAR(*name))) { /* INTL: digit */ + if (Tcl_GetInt(interp, name, &level) != TCL_OK) { + goto levelError; + } + level = curLevel - level; + } else { + level = curLevel - 1; + result = 0; + } + + /* + * Figure out which frame to use, and return it to the caller. + */ + + for (framePtr = iPtr->varFramePtr; framePtr != NULL; + framePtr = framePtr->callerVarPtr) { + if (framePtr->level == level) { + break; + } + } + if (framePtr == NULL) { + goto levelError; + } + + *framePtrPtr = framePtr; + return result; + + levelError: + Tcl_ResetResult(interp); + Tcl_AppendResult(interp, "bad level \"", name, "\"", NULL); + return -1; +} + +/* + *---------------------------------------------------------------------- + * + * TclObjGetFrame -- + * + * Given a description of a procedure frame, such as the first argument + * to an "uplevel" or "upvar" command, locate the call frame for the + * appropriate level of procedure. + * + * Results: + * The return value is -1 if an error occurred in finding the frame (in + * this case an error message is left in the interp's result). 1 is + * returned if objPtr was either a number or a number preceded by "#" and + * it specified a valid frame. 0 is returned if objPtr isn't one of the + * two things above (in this case, the lookup acts as if objPtr were + * "1"). The variable pointed to by framePtrPtr is filled in with the + * address of the desired frame (unless an error occurs, in which case it + * isn't modified). + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +int +TclObjGetFrame( + Tcl_Interp *interp, /* Interpreter in which to find frame. */ + Tcl_Obj *objPtr, /* Object describing frame. */ + CallFrame **framePtrPtr) /* Store pointer to frame here (or NULL if + * global frame indicated). */ +{ + register Interp *iPtr = (Interp *) interp; + int curLevel, level, result; + CallFrame *framePtr; + CONST char *name = TclGetString(objPtr); + + /* + * Parse object to figure out which level number to go to. + */ + + result = 1; + curLevel = iPtr->varFramePtr->level; + if (objPtr->typePtr == &levelReferenceType) { + if (PTR2INT(objPtr->internalRep.twoPtrValue.ptr1)) { + level = curLevel - PTR2INT(objPtr->internalRep.twoPtrValue.ptr2); + } else { + level = PTR2INT(objPtr->internalRep.twoPtrValue.ptr2); } if (level < 0) { - levelError: - Tcl_AppendResult(interp, "bad level \"", string, "\"", - (char *) NULL); - return -1; + goto levelError; + } + /* TODO: Consider skipping the typePtr checks */ + } else if (objPtr->typePtr == &tclIntType +#ifndef NO_WIDE_TYPE + || objPtr->typePtr == &tclWideIntType +#endif + ) { + if (TclGetIntFromObj(NULL, objPtr, &level) != TCL_OK || level < 0) { + goto levelError; + } + level = curLevel - level; + } else if (*name == '#') { + if (Tcl_GetInt(interp, name+1, &level) != TCL_OK || level < 0) { + goto levelError; } - } else if (isdigit(UCHAR(*string))) { /* INTL: digit */ - if (Tcl_GetInt(interp, string, &level) != TCL_OK) { + + /* + * Cache for future reference. + * + * TODO: Use the new ptrAndLongRep intrep + */ + + TclFreeIntRep(objPtr); + objPtr->typePtr = &levelReferenceType; + objPtr->internalRep.twoPtrValue.ptr1 = (void *) 0; + objPtr->internalRep.twoPtrValue.ptr2 = INT2PTR(level); + } else if (isdigit(UCHAR(*name))) { /* INTL: digit */ + if (Tcl_GetInt(interp, name, &level) != TCL_OK) { return -1; } + + /* + * Cache for future reference. + * + * TODO: Use the new ptrAndLongRep intrep + */ + + TclFreeIntRep(objPtr); + objPtr->typePtr = &levelReferenceType; + objPtr->internalRep.twoPtrValue.ptr1 = (void *) 1; + objPtr->internalRep.twoPtrValue.ptr2 = INT2PTR(level); level = curLevel - level; } else { + /* + * Don't cache as the object *isn't* a level reference. + */ + level = curLevel - 1; result = 0; } /* - * Figure out which frame to use, and modify the interpreter so - * its variables come from that frame. + * Figure out which frame to use, and return it to the caller. */ - if (level == 0) { - framePtr = NULL; - } else { - for (framePtr = iPtr->varFramePtr; framePtr != NULL; - framePtr = framePtr->callerVarPtr) { - if (framePtr->level == level) { - break; - } - } - if (framePtr == NULL) { - goto levelError; + for (framePtr = iPtr->varFramePtr; framePtr != NULL; + framePtr = framePtr->callerVarPtr) { + if (framePtr->level == level) { + break; } } + if (framePtr == NULL) { + goto levelError; + } *framePtrPtr = framePtr; return result; + + levelError: + Tcl_ResetResult(interp); + Tcl_AppendResult(interp, "bad level \"", name, "\"", NULL); + return -1; } /* @@ -706,8 +878,8 @@ TclGetFrame(interp, string, framePtrPtr) * * Tcl_UplevelObjCmd -- * - * This object procedure is invoked to process the "uplevel" Tcl - * command. See the user documentation for details on what it does. + * This object function is invoked to process the "uplevel" Tcl command. + * See the user documentation for details on what it does. * * Results: * A standard Tcl object result value. @@ -720,19 +892,18 @@ TclGetFrame(interp, string, framePtrPtr) /* ARGSUSED */ int -Tcl_UplevelObjCmd(dummy, interp, objc, objv) - ClientData dummy; /* Not used. */ - Tcl_Interp *interp; /* Current interpreter. */ - int objc; /* Number of arguments. */ - Tcl_Obj *CONST objv[]; /* Argument objects. */ +Tcl_UplevelObjCmd( + ClientData dummy, /* Not used. */ + Tcl_Interp *interp, /* Current interpreter. */ + int objc, /* Number of arguments. */ + Tcl_Obj *CONST objv[]) /* Argument objects. */ { register Interp *iPtr = (Interp *) interp; - char *optLevel; int result; CallFrame *savedVarFramePtr, *framePtr; if (objc < 2) { - uplevelSyntax: + uplevelSyntax: Tcl_WrongNumArgs(interp, 1, objv, "?level? command ?arg ...?"); return TCL_ERROR; } @@ -741,8 +912,7 @@ Tcl_UplevelObjCmd(dummy, interp, objc, objv) * Find the level to use for executing the command. */ - optLevel = TclGetString(objv[1]); - result = TclGetFrame(interp, optLevel, &framePtr); + result = TclObjGetFrame(interp, objv[1], &framePtr); if (result == -1) { return TCL_ERROR; } @@ -764,30 +934,30 @@ Tcl_UplevelObjCmd(dummy, interp, objc, objv) */ if (objc == 1) { -#ifdef TCL_TIP280 - /* TIP #280. Make argument location available to eval'd script */ + /* + * TIP #280. Make argument location available to eval'd script + */ + CmdFrame* invoker = NULL; int word = 0; + TclArgumentGet (interp, objv[0], &invoker, &word); - result = TclEvalObjEx(interp, objv[0], TCL_EVAL_DIRECT, invoker, word); -#else - result = Tcl_EvalObjEx(interp, objv[0], TCL_EVAL_DIRECT); -#endif + result = TclEvalObjEx(interp, objv[0], 0, invoker, word); } else { /* * More than one argument: concatenate them together with spaces - * between, then evaluate the result. Tcl_EvalObjEx will delete - * the object when it decrements its refcount after eval'ing it. + * between, then evaluate the result. Tcl_EvalObjEx will delete the + * object when it decrements its refcount after eval'ing it. */ + Tcl_Obj *objPtr; objPtr = Tcl_ConcatObj(objc, objv); result = Tcl_EvalObjEx(interp, objPtr, TCL_EVAL_DIRECT); } if (result == TCL_ERROR) { - char msg[32 + TCL_INTEGER_SPACE]; - sprintf(msg, "\n (\"uplevel\" body line %d)", interp->errorLine); - Tcl_AddObjErrorInfo(interp, msg, -1); + Tcl_AppendObjToErrorInfo(interp, Tcl_ObjPrintf( + "\n (\"uplevel\" body line %d)", interp->errorLine)); } /* @@ -803,18 +973,17 @@ Tcl_UplevelObjCmd(dummy, interp, objc, objv) * * TclFindProc -- * - * Given the name of a procedure, return a pointer to the - * record describing the procedure. The procedure will be - * looked up using the usual rules: first in the current - * namespace and then in the global namespace. + * Given the name of a procedure, return a pointer to the record + * describing the procedure. The procedure will be looked up using the + * usual rules: first in the current namespace and then in the global + * namespace. * * Results: - * NULL is returned if the name doesn't correspond to any - * procedure. Otherwise, the return value is a pointer to - * the procedure's record. If the name is found but refers - * to an imported command that points to a "real" procedure - * defined in another namespace, a pointer to that "real" - * procedure's structure is returned. + * NULL is returned if the name doesn't correspond to any procedure. + * Otherwise, the return value is a pointer to the procedure's record. If + * the name is found but refers to an imported command that points to a + * "real" procedure defined in another namespace, a pointer to that + * "real" procedure's structure is returned. * * Side effects: * None. @@ -823,18 +992,17 @@ Tcl_UplevelObjCmd(dummy, interp, objc, objv) */ Proc * -TclFindProc(iPtr, procName) - Interp *iPtr; /* Interpreter in which to look. */ - CONST char *procName; /* Name of desired procedure. */ +TclFindProc( + Interp *iPtr, /* Interpreter in which to look. */ + CONST char *procName) /* Name of desired procedure. */ { Tcl_Command cmd; Tcl_Command origCmd; Command *cmdPtr; - cmd = Tcl_FindCommand((Tcl_Interp *) iPtr, procName, - (Tcl_Namespace *) NULL, /*flags*/ 0); + cmd = Tcl_FindCommand((Tcl_Interp *) iPtr, procName, NULL, /*flags*/ 0); if (cmd == (Tcl_Command) NULL) { - return NULL; + return NULL; } cmdPtr = (Command *) cmd; @@ -842,10 +1010,10 @@ TclFindProc(iPtr, procName) if (origCmd != NULL) { cmdPtr = (Command *) origCmd; } - if (cmdPtr->proc != TclProcInterpProc) { + if (cmdPtr->objProc != TclObjInterpProc) { return NULL; } - return (Proc *) cmdPtr->clientData; + return (Proc *) cmdPtr->objClientData; } /* @@ -856,9 +1024,9 @@ TclFindProc(iPtr, procName) * Tells whether a command is a Tcl procedure or not. * * Results: - * If the given command is actually a Tcl procedure, the - * return value is the address of the record describing - * the procedure. Otherwise the return value is 0. + * If the given command is actually a Tcl procedure, the return value is + * the address of the record describing the procedure. Otherwise the + * return value is 0. * * Side effects: * None. @@ -867,8 +1035,8 @@ TclFindProc(iPtr, procName) */ Proc * -TclIsProc(cmdPtr) - Command *cmdPtr; /* Command to test. */ +TclIsProc( + Command *cmdPtr) /* Command to test. */ { Tcl_Command origCmd; @@ -876,8 +1044,8 @@ TclIsProc(cmdPtr) if (origCmd != NULL) { cmdPtr = (Command *) origCmd; } - if (cmdPtr->proc == TclProcInterpProc) { - return (Proc *) cmdPtr->clientData; + if (cmdPtr->objProc == TclObjInterpProc) { + return (Proc *) cmdPtr->objClientData; } return (Proc *) 0; } @@ -885,310 +1053,659 @@ TclIsProc(cmdPtr) /* *---------------------------------------------------------------------- * - * TclProcInterpProc -- + * InitArgsAndLocals -- * - * When a Tcl procedure gets invoked with an argc/argv array of - * strings, this routine gets invoked to interpret the procedure. + * This routine is invoked in order to initialize the arguments and other + * compiled locals table for a new call frame. * * Results: - * A standard Tcl result value, usually TCL_OK. + * A standard Tcl result. * * Side effects: - * Depends on the commands in the procedure. + * Allocates memory on the stack for the compiled local variables, the + * caller is responsible for freeing them. Initialises all variables. May + * invoke various name resolvers in order to determine which variables + * are being referenced at runtime. * *---------------------------------------------------------------------- */ -int -TclProcInterpProc(clientData, interp, argc, argv) - ClientData clientData; /* Record describing procedure to be - * interpreted. */ - Tcl_Interp *interp; /* Interpreter in which procedure was - * invoked. */ - int argc; /* Count of number of arguments to this - * procedure. */ - register CONST char **argv; /* Argument values. */ +static int +ProcWrongNumArgs( + Tcl_Interp *interp, int skip) { - register Tcl_Obj *objPtr; - register int i; - int result; + CallFrame *framePtr = ((Interp *)interp)->varFramePtr; + register Proc *procPtr = framePtr->procPtr; + register Var *defPtr; + int localCt = procPtr->numCompiledLocals, numArgs, i; + Tcl_Obj **desiredObjs; + const char *final = NULL; /* - * This procedure generates an objv array for object arguments that hold - * the argv strings. It starts out with stack-allocated space but uses - * dynamically-allocated storage if needed. + * Build up desired argument list for Tcl_WrongNumArgs */ -#define NUM_ARGS 20 - Tcl_Obj *(objStorage[NUM_ARGS]); - register Tcl_Obj **objv = objStorage; + numArgs = framePtr->procPtr->numArgs; + desiredObjs = (Tcl_Obj **) TclStackAlloc(interp, + (int) sizeof(Tcl_Obj *) * (numArgs+1)); - /* - * Create the object argument array "objv". Make sure objv is large - * enough to hold the objc arguments plus 1 extra for the zero - * end-of-objv word. - */ + if (framePtr->isProcCallFrame & FRAME_IS_LAMBDA) { + desiredObjs[0] = Tcl_NewStringObj("lambdaExpr", -1); + } else { + ((Interp *) interp)->ensembleRewrite.numInsertedObjs -= skip - 1; - if ((argc + 1) > NUM_ARGS) { - objv = (Tcl_Obj **) - ckalloc((unsigned)(argc + 1) * sizeof(Tcl_Obj *)); +#ifdef AVOID_HACKS_FOR_ITCL + desiredObjs[0] = framePtr->objv[skip-1]; +#else + desiredObjs[0] = Tcl_NewListObj(skip, framePtr->objv); +#endif /* AVOID_HACKS_FOR_ITCL */ } - - for (i = 0; i < argc; i++) { - objv[i] = Tcl_NewStringObj(argv[i], -1); - Tcl_IncrRefCount(objv[i]); + Tcl_IncrRefCount(desiredObjs[0]); + + defPtr = (Var *) (&framePtr->localCachePtr->varName0 + localCt); + for (i=1 ; i<=numArgs ; i++, defPtr++) { + Tcl_Obj *argObj; + Tcl_Obj *namePtr = localName(framePtr, i-1); + + if (defPtr->value.objPtr != NULL) { + TclNewObj(argObj); + Tcl_AppendStringsToObj(argObj, "?", TclGetString(namePtr), "?", NULL); + } else if (defPtr->flags & VAR_IS_ARGS) { + numArgs--; + final = "..."; + break; + } else { + argObj = namePtr; + Tcl_IncrRefCount(namePtr); + } + desiredObjs[i] = argObj; } - objv[argc] = 0; - - /* - * Use TclObjInterpProc to actually interpret the procedure. - */ - - result = TclObjInterpProc(clientData, interp, argc, objv); - /* - * Move the interpreter's object result to the string result, - * then reset the object result. - */ + Tcl_ResetResult(interp); + Tcl_WrongNumArgs(interp, numArgs+1, desiredObjs, final); - Tcl_SetResult(interp, TclGetString(Tcl_GetObjResult(interp)), - TCL_VOLATILE); + for (i=0 ; i<=numArgs ; i++) { + Tcl_DecrRefCount(desiredObjs[i]); + } + TclStackFree(interp, desiredObjs); + return TCL_ERROR; +} + +/* + *---------------------------------------------------------------------- + * + * TclInitCompiledLocals -- + * + * This routine is invoked in order to initialize the compiled locals + * table for a new call frame. + * + * DEPRECATED: functionality has been inlined elsewhere; this function + * remains to insure binary compatibility with Itcl. + * - /* - * Decrement the ref counts on the objv elements since we are done - * with them. - */ + * Results: + * None. + * + * Side effects: + * May invoke various name resolvers in order to determine which + * variables are being referenced at runtime. + * + *---------------------------------------------------------------------- + */ +void +TclInitCompiledLocals( + Tcl_Interp *interp, /* Current interpreter. */ + CallFrame *framePtr, /* Call frame to initialize. */ + Namespace *nsPtr) /* Pointer to current namespace. */ +{ + Var *varPtr = framePtr->compiledLocals; + Tcl_Obj *bodyPtr; + ByteCode *codePtr; - for (i = 0; i < argc; i++) { - objPtr = objv[i]; - TclDecrRefCount(objPtr); + bodyPtr = framePtr->procPtr->bodyPtr; + if (bodyPtr->typePtr != &tclByteCodeType) { + Tcl_Panic("body object for proc attached to frame is not a byte code type"); } + codePtr = bodyPtr->internalRep.otherValuePtr; - /* - * Free the objv array if malloc'ed storage was used. - */ - - if (objv != objStorage) { - ckfree((char *) objv); + if (framePtr->numCompiledLocals) { + if (!codePtr->localCachePtr) { + InitLocalCache(framePtr->procPtr) ; + } + framePtr->localCachePtr = codePtr->localCachePtr; + framePtr->localCachePtr->refCount++; } - return result; -#undef NUM_ARGS + + InitResolvedLocals(interp, codePtr, varPtr, nsPtr); } /* *---------------------------------------------------------------------- * - * TclObjInterpProc -- + * InitResolvedLocals -- * - * When a Tcl procedure gets invoked during bytecode evaluation, this - * object-based routine gets invoked to interpret the procedure. + * This routine is invoked in order to initialize the compiled locals + * table for a new call frame. * * Results: - * A standard Tcl object result value. + * None. * * Side effects: - * Depends on the commands in the procedure. + * May invoke various name resolvers in order to determine which + * variables are being referenced at runtime. * *---------------------------------------------------------------------- */ -int -TclObjInterpProc(clientData, interp, objc, objv) - ClientData clientData; /* Record describing procedure to be - * interpreted. */ - register Tcl_Interp *interp; /* Interpreter in which procedure was - * invoked. */ - int objc; /* Count of number of arguments to this - * procedure. */ - Tcl_Obj *CONST objv[]; /* Argument value objects. */ +static void +InitResolvedLocals( + Tcl_Interp *interp, /* Current interpreter. */ + ByteCode *codePtr, + Var *varPtr, + Namespace *nsPtr) /* Pointer to current namespace. */ { Interp *iPtr = (Interp *) interp; - Proc *procPtr = (Proc *) clientData; - Namespace *nsPtr = procPtr->cmdPtr->nsPtr; - CallFrame frame; - register CallFrame *framePtr = &frame; - register Var *varPtr; - register CompiledLocal *localPtr; - char *procName; - int nameLen, localCt, numArgs, argCt, i, result; + int haveResolvers = (nsPtr->compiledVarResProc || iPtr->resolverPtr); + CompiledLocal *firstLocalPtr, *localPtr; + int varNum; + Tcl_ResolvedVarInfo *resVarInfo; /* - * This procedure generates an array "compiledLocals" that holds the - * storage for local variables. It starts out with stack-allocated space - * but uses dynamically-allocated storage if needed. + * Find the localPtr corresponding to varPtr */ -#define NUM_LOCALS 20 - Var localStorage[NUM_LOCALS]; - Var *compiledLocals = localStorage; + varNum = varPtr - iPtr->framePtr->compiledLocals; + localPtr = iPtr->framePtr->procPtr->firstLocalPtr; + while (varNum--) { + localPtr = localPtr->nextPtr; + } - /* - * Get the procedure's name. - */ + if (!(haveResolvers && (codePtr->flags & TCL_BYTECODE_RESOLVE_VARS))) { + /* + * Initialize the array of local variables stored in the call frame. + * Some variables may have special resolution rules. In that case, we + * call their "resolver" procs to get our hands on the variable, and + * we make the compiled local a link to the real variable. + */ - procName = Tcl_GetStringFromObj(objv[0], &nameLen); + doInitResolvedLocals: + for (; localPtr != NULL; varPtr++, localPtr = localPtr->nextPtr) { + varPtr->flags = 0; + varPtr->value.objPtr = NULL; + + /* + * Now invoke the resolvers to determine the exact variables + * that should be used. + */ + + resVarInfo = localPtr->resolveInfo; + if (resVarInfo && resVarInfo->fetchProc) { + Var *resolvedVarPtr = (Var *) + (*resVarInfo->fetchProc)(interp, resVarInfo); + if (resolvedVarPtr) { + if (TclIsVarInHash(resolvedVarPtr)) { + VarHashRefCount(resolvedVarPtr)++; + } + varPtr->flags = VAR_LINK; + varPtr->value.linkPtr = resolvedVarPtr; + } + } + } + return; + } /* - * If necessary, compile the procedure's body. The compiler will - * allocate frame slots for the procedure's non-argument local - * variables. Note that compiling the body might increase - * procPtr->numCompiledLocals if new local variables are found - * while compiling. + * This is the first run after a recompile, or else the resolver epoch + * has changed: update the resolver cache. */ - result = ProcCompileProc(interp, procPtr, procPtr->bodyPtr, nsPtr, - "body of proc", procName, &procPtr); + firstLocalPtr = localPtr; + for (; localPtr != NULL; localPtr = localPtr->nextPtr) { + if (localPtr->resolveInfo) { + if (localPtr->resolveInfo->deleteProc) { + localPtr->resolveInfo->deleteProc(localPtr->resolveInfo); + } else { + ckfree((char *) localPtr->resolveInfo); + } + localPtr->resolveInfo = NULL; + } + localPtr->flags &= ~VAR_RESOLVED; + + if (haveResolvers && + !(localPtr->flags & (VAR_ARGUMENT|VAR_TEMPORARY))) { + ResolverScheme *resPtr = iPtr->resolverPtr; + Tcl_ResolvedVarInfo *vinfo; + int result; + + if (nsPtr->compiledVarResProc) { + result = (*nsPtr->compiledVarResProc)(nsPtr->interp, + localPtr->name, localPtr->nameLength, + (Tcl_Namespace *) nsPtr, &vinfo); + } else { + result = TCL_CONTINUE; + } - if (result != TCL_OK) { - return result; + while ((result == TCL_CONTINUE) && resPtr) { + if (resPtr->compiledVarResProc) { + result = (*resPtr->compiledVarResProc)(nsPtr->interp, + localPtr->name, localPtr->nameLength, + (Tcl_Namespace *) nsPtr, &vinfo); + } + resPtr = resPtr->nextPtr; + } + if (result == TCL_OK) { + localPtr->resolveInfo = vinfo; + localPtr->flags |= VAR_RESOLVED; + } + } } + localPtr = firstLocalPtr; + codePtr->flags &= ~TCL_BYTECODE_RESOLVE_VARS; + goto doInitResolvedLocals; +} - /* - * Create the "compiledLocals" array. Make sure it is large enough to - * hold all the procedure's compiled local variables, including its - * formal parameters. - */ +void +TclFreeLocalCache( + Tcl_Interp *interp, + LocalCache *localCachePtr) +{ + int i; + Tcl_Obj **namePtrPtr = &localCachePtr->varName0; - localCt = procPtr->numCompiledLocals; - if (localCt > NUM_LOCALS) { - compiledLocals = (Var *) ckalloc((unsigned) localCt * sizeof(Var)); + for (i = 0; i < localCachePtr->numVars; i++, namePtrPtr++) { + Tcl_Obj *objPtr = *namePtrPtr; + /* + * Note that this can be called with interp==NULL, on interp + * deletion. In that case, the literal table and objects go away + * on their own. + */ + if (objPtr) { + if (interp) { + TclReleaseLiteral(interp, objPtr); + } else { + Tcl_DecrRefCount(objPtr); + } + } } + ckfree((char *) localCachePtr); +} + +static void +InitLocalCache(Proc *procPtr) +{ + Interp *iPtr = procPtr->iPtr; + ByteCode *codePtr = procPtr->bodyPtr->internalRep.otherValuePtr; + int localCt = procPtr->numCompiledLocals; + int numArgs = procPtr->numArgs, i = 0; + + Tcl_Obj **namePtr; + Var *varPtr; + LocalCache *localCachePtr; + CompiledLocal *localPtr; + int new; /* - * Set up and push a new call frame for the new procedure invocation. - * This call frame will execute in the proc's namespace, which might - * be different than the current namespace. The proc's namespace is - * that of its command, which can change if the command is renamed - * from one namespace to another. + * Cache the names and initial values of local variables; store the + * cache in both the framePtr for this execution and in the codePtr + * for future calls. */ - result = Tcl_PushCallFrame(interp, (Tcl_CallFrame *) framePtr, - (Tcl_Namespace *) nsPtr, /*isProcCallFrame*/ 1); + localCachePtr = (LocalCache *) ckalloc(sizeof(LocalCache) + + (localCt-1)*sizeof(Tcl_Obj *) + + numArgs*sizeof(Var)); - if (result != TCL_OK) { - return result; + namePtr = &localCachePtr->varName0; + varPtr = (Var *) (namePtr + localCt); + localPtr = procPtr->firstLocalPtr; + while (localPtr) { + if (TclIsVarTemporary(localPtr)) { + *namePtr = NULL; + } else { + *namePtr = TclCreateLiteral(iPtr, localPtr->name, + localPtr->nameLength, /* hash */ (unsigned int) -1, + &new, /* nsPtr */ NULL, 0, NULL); + Tcl_IncrRefCount(*namePtr); + } + + if (i < numArgs) { + varPtr->flags = (localPtr->flags & VAR_IS_ARGS); + varPtr->value.objPtr = localPtr->defValuePtr; + varPtr++; + i++; + } + namePtr++; + localPtr=localPtr->nextPtr; } + codePtr->localCachePtr = localCachePtr; + localCachePtr->refCount = 1; + localCachePtr->numVars = localCt; +} - framePtr->objc = objc; - framePtr->objv = objv; /* ref counts for args are incremented below */ +static int +InitArgsAndLocals( + register Tcl_Interp *interp,/* Interpreter in which procedure was + * invoked. */ + Tcl_Obj *procNameObj, /* Procedure name for error reporting. */ + int skip) /* Number of initial arguments to be skipped, + * i.e., words in the "command name". */ +{ + CallFrame *framePtr = ((Interp *)interp)->varFramePtr; + register Proc *procPtr = framePtr->procPtr; + ByteCode *codePtr = procPtr->bodyPtr->internalRep.otherValuePtr; + register Var *varPtr, *defPtr; + int localCt = procPtr->numCompiledLocals, numArgs, argCt, i, imax; + Tcl_Obj *const *argObjs; /* - * Initialize and resolve compiled variable references. + * Make sure that the local cache of variable names and initial values has + * been initialised properly . */ - framePtr->procPtr = procPtr; - framePtr->numCompiledLocals = localCt; - framePtr->compiledLocals = compiledLocals; + if (localCt) { + if (!codePtr->localCachePtr) { + InitLocalCache(procPtr) ; + } + framePtr->localCachePtr = codePtr->localCachePtr; + framePtr->localCachePtr->refCount++; + defPtr = (Var *) (&framePtr->localCachePtr->varName0 + localCt); + } else { + defPtr = NULL; + } + + /* + * Create the "compiledLocals" array. Make sure it is large enough to hold + * all the procedure's compiled local variables, including its formal + * parameters. + */ - TclInitCompiledLocals(interp, framePtr, nsPtr); + varPtr = (Var*) TclStackAlloc(interp, (int)(localCt*sizeof(Var))); + framePtr->compiledLocals = varPtr; + framePtr->numCompiledLocals = localCt; /* - * Match and assign the call's actual parameters to the procedure's - * formal arguments. The formal arguments are described by the first - * numArgs entries in both the Proc structure's local variable list and - * the call frame's local variable array. + * Match and assign the call's actual parameters to the procedure's formal + * arguments. The formal arguments are described by the first numArgs + * entries in both the Proc structure's local variable list and the call + * frame's local variable array. */ numArgs = procPtr->numArgs; - varPtr = framePtr->compiledLocals; - localPtr = procPtr->firstLocalPtr; - argCt = objc; - for (i = 1, argCt -= 1; i <= numArgs; i++, argCt--) { - if (!TclIsVarArgument(localPtr)) { - panic("TclObjInterpProc: local variable %s is not argument but should be", - localPtr->name); - return TCL_ERROR; - } - if (TclIsVarTemporary(localPtr)) { - panic("TclObjInterpProc: local variable %d is temporary but should be an argument", i); - return TCL_ERROR; + argCt = framePtr->objc - skip; /* Set it to the number of args to the + * procedure. */ + argObjs = framePtr->objv + skip; + if (numArgs == 0) { + if (argCt) { + goto incorrectArgs; + } else { + goto correctArgs; } + } + imax = ((argCt < numArgs-1) ? argCt : numArgs-1); + for (i = 0; i < imax; i++, varPtr++, defPtr++) { + /* + * "Normal" arguments; last formal is special, depends on it being + * 'args'. + */ + + Tcl_Obj *objPtr = argObjs[i]; + varPtr->flags = 0; + varPtr->value.objPtr = objPtr; + Tcl_IncrRefCount(objPtr); /* Local var is a reference. */ + } + for (; i < numArgs-1; i++, varPtr++, defPtr++) { /* - * Handle the special case of the last formal being "args". When - * it occurs, assign it a list consisting of all the remaining - * actual arguments. + * This loop is entered if argCt < (numArgs-1). Set default values; + * last formal is special. */ - if ((i == numArgs) && ((localPtr->name[0] == 'a') - && (strcmp(localPtr->name, "args") == 0))) { - Tcl_Obj *listPtr = Tcl_NewListObj(argCt, &(objv[i])); - varPtr->value.objPtr = listPtr; - Tcl_IncrRefCount(listPtr); /* local var is a reference */ - TclClearVarUndefined(varPtr); - argCt = 0; - break; /* done processing args */ - } else if (argCt > 0) { - Tcl_Obj *objPtr = objv[i]; - varPtr->value.objPtr = objPtr; - TclClearVarUndefined(varPtr); - Tcl_IncrRefCount(objPtr); /* since the local variable now has - * another reference to object. */ - } else if (localPtr->defValuePtr != NULL) { - Tcl_Obj *objPtr = localPtr->defValuePtr; + Tcl_Obj *objPtr = defPtr->value.objPtr; + + if (objPtr) { + varPtr->flags = 0; varPtr->value.objPtr = objPtr; - TclClearVarUndefined(varPtr); - Tcl_IncrRefCount(objPtr); /* since the local variable now has - * another reference to object. */ + Tcl_IncrRefCount(objPtr); /* Local var reference. */ } else { goto incorrectArgs; } - varPtr++; - localPtr = localPtr->nextPtr; } - if (argCt > 0) { - Tcl_Obj *objResult; - int len, flags; - incorrectArgs: - /* - * Build up equivalent to Tcl_WrongNumArgs message for proc - */ + /* + * When we get here, the last formal argument remains to be defined: + * defPtr and varPtr point to the last argument to be initialized. + */ - Tcl_ResetResult(interp); - objResult = Tcl_GetObjResult(interp); - Tcl_AppendToObj(objResult, "wrong # args: should be \"", -1); - /* - * Quote the proc name if it contains spaces (Bug 942757). - */ + varPtr->flags = 0; + if (defPtr->flags & VAR_IS_ARGS) { + Tcl_Obj *listPtr = Tcl_NewListObj(argCt-i, argObjs+i); + + varPtr->value.objPtr = listPtr; + Tcl_IncrRefCount(listPtr); /* Local var is a reference. */ + } else if (argCt == numArgs) { + Tcl_Obj *objPtr = argObjs[i]; - len = Tcl_ScanCountedElement(procName, nameLen, &flags); - if (len != nameLen) { - char *procName1 = ckalloc((unsigned) len + 1); - len = Tcl_ConvertCountedElement(procName, nameLen, procName1, flags); - Tcl_AppendToObj(objResult, procName1, len); - ckfree(procName1); + varPtr->value.objPtr = objPtr; + Tcl_IncrRefCount(objPtr); /* Local var is a reference. */ + } else if ((argCt < numArgs) && (defPtr->value.objPtr != NULL)) { + Tcl_Obj *objPtr = defPtr->value.objPtr; + + varPtr->value.objPtr = objPtr; + Tcl_IncrRefCount(objPtr); /* Local var is a reference. */ + } else { + goto incorrectArgs; + } + varPtr++; + + /* + * Initialise and resolve the remaining compiledLocals. In the absence of + * resolvers, they are undefined local vars: (flags=0, value=NULL). + */ + + correctArgs: + if (numArgs < localCt) { + if (!framePtr->nsPtr->compiledVarResProc && !((Interp *)interp)->resolverPtr) { + memset(varPtr, 0, (localCt - numArgs)*sizeof(Var)); } else { - Tcl_AppendToObj(objResult, procName, len); + InitResolvedLocals(interp, codePtr, varPtr, framePtr->nsPtr); } + } - localPtr = procPtr->firstLocalPtr; - for (i = 1; i <= numArgs; i++) { - if (localPtr->defValuePtr != NULL) { - Tcl_AppendStringsToObj(objResult, - " ?", localPtr->name, "?", (char *) NULL); - } else { - Tcl_AppendStringsToObj(objResult, - " ", localPtr->name, (char *) NULL); - } - localPtr = localPtr->nextPtr; - } - Tcl_AppendStringsToObj(objResult, "\"", (char *) NULL); + return TCL_OK; - result = TCL_ERROR; - goto procDone; + + incorrectArgs: + /* + * Initialise all compiled locals to avoid problems at DeleteLocalVars. + */ + + memset(varPtr, 0, ((framePtr->compiledLocals + localCt)-varPtr)*sizeof(Var)); + return ProcWrongNumArgs(interp, skip); +} + +/* + *---------------------------------------------------------------------- + * + * PushProcCallFrame -- + * + * Compiles a proc body if necessary, then pushes a CallFrame suitable + * for executing it. + * + * Results: + * A standard Tcl object result value. + * + * Side effects: + * The proc's body may be recompiled. A CallFrame is pushed, it will have + * to be popped by the caller. + * + *---------------------------------------------------------------------- + */ + +static int +PushProcCallFrame( + ClientData clientData, /* Record describing procedure to be + * interpreted. */ + register Tcl_Interp *interp,/* Interpreter in which procedure was + * invoked. */ + int objc, /* Count of number of arguments to this + * procedure. */ + Tcl_Obj *CONST objv[], /* Argument value objects. */ + int isLambda) /* 1 if this is a call by ApplyObjCmd: it + * needs special rules for error msg */ +{ + Proc *procPtr = (Proc *) clientData; + Namespace *nsPtr = procPtr->cmdPtr->nsPtr; + CallFrame *framePtr, **framePtrPtr; + int result; + ByteCode *codePtr; + + /* + * If necessary (i.e. if we haven't got a suitable compilation already + * cached) compile the procedure's body. The compiler will allocate frame + * slots for the procedure's non-argument local variables. Note that + * compiling the body might increase procPtr->numCompiledLocals if new + * local variables are found while compiling. + */ + + if (procPtr->bodyPtr->typePtr == &tclByteCodeType) { + Interp *iPtr = (Interp *) interp; + + /* + * When we've got bytecode, this is the check for validity. That is, + * the bytecode must be for the right interpreter (no cross-leaks!), + * the code must be from the current epoch (so subcommand compilation + * is up-to-date), the namespace must match (so variable handling + * is right) and the resolverEpoch must match (so that new shadowed + * commands and/or resolver changes are considered). + */ + + codePtr = procPtr->bodyPtr->internalRep.otherValuePtr; + if (((Interp *) *codePtr->interpHandle != iPtr) + || (codePtr->compileEpoch != iPtr->compileEpoch) + || (codePtr->nsPtr != nsPtr) + || (codePtr->nsEpoch != nsPtr->resolverEpoch)) { + goto doCompilation; + } + } else { + doCompilation: + result = ProcCompileProc(interp, procPtr, procPtr->bodyPtr, nsPtr, + (isLambda ? "body of lambda term" : "body of proc"), + TclGetString(objv[isLambda]), &procPtr); + if (result != TCL_OK) { + return result; + } } /* - * Invoke the commands in the procedure's body. + * Set up and push a new call frame for the new procedure invocation. + * This call frame will execute in the proc's namespace, which might be + * different than the current namespace. The proc's namespace is that of + * its command, which can change if the command is renamed from one + * namespace to another. */ -#ifdef TCL_COMPILE_DEBUG + framePtrPtr = &framePtr; + result = TclPushStackFrame(interp, (Tcl_CallFrame **) framePtrPtr, + (Tcl_Namespace *) nsPtr, + (isLambda? (FRAME_IS_PROC|FRAME_IS_LAMBDA) : FRAME_IS_PROC)); + if (result != TCL_OK) { + return result; + } + + framePtr->objc = objc; + framePtr->objv = objv; + framePtr->procPtr = procPtr; + + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * TclObjInterpProc -- + * + * When a Tcl procedure gets invoked during bytecode evaluation, this + * object-based routine gets invoked to interpret the procedure. + * + * Results: + * A standard Tcl object result value. + * + * Side effects: + * Depends on the commands in the procedure. + * + *---------------------------------------------------------------------- + */ + +int +TclObjInterpProc( + ClientData clientData, /* Record describing procedure to be + * interpreted. */ + register Tcl_Interp *interp,/* Interpreter in which procedure was + * invoked. */ + int objc, /* Count of number of arguments to this + * procedure. */ + Tcl_Obj *CONST objv[]) /* Argument value objects. */ +{ + int result; + + result = PushProcCallFrame(clientData, interp, objc, objv, /*isLambda*/ 0); + if (result == TCL_OK) { + return TclObjInterpProcCore(interp, objv[0], 1, &MakeProcError); + } else { + return TCL_ERROR; + } +} + +/* + *---------------------------------------------------------------------- + * + * TclObjInterpProcCore -- + * + * When a Tcl procedure, lambda term or anything else that works like a + * procedure gets invoked during bytecode evaluation, this object-based + * routine gets invoked to interpret the body. + * + * Results: + * A standard Tcl object result value. + * + * Side effects: + * Nearly anything; depends on the commands in the procedure body. + * + *---------------------------------------------------------------------- + */ + +int +TclObjInterpProcCore( + register Tcl_Interp *interp,/* Interpreter in which procedure was + * invoked. */ + Tcl_Obj *procNameObj, /* Procedure name for error reporting. */ + int skip, /* Number of initial arguments to be skipped, + * i.e., words in the "command name". */ + ProcErrorProc errorProc) /* How to convert results from the script into + * results of the overall procedure. */ +{ + Interp *iPtr = (Interp *) interp; + register Proc *procPtr = iPtr->varFramePtr->procPtr; + int result; + CallFrame *freePtr; + + result = InitArgsAndLocals(interp, procNameObj, skip); + if (result != TCL_OK) { + goto procDone; + } + +#if defined(TCL_COMPILE_DEBUG) if (tclTraceExec >= 1) { - fprintf(stdout, "Calling proc "); - for (i = 0; i < objc; i++) { - TclPrintObject(stdout, objv[i], 15); + register CallFrame *framePtr = iPtr->varFramePtr; + register int i; + + if (framePtr->isProcCallFrame & FRAME_IS_LAMBDA) { + fprintf(stdout, "Calling lambda "); + } else { + fprintf(stdout, "Calling proc "); + } + for (i = 0; i < framePtr->objc; i++) { + TclPrintObject(stdout, framePtr->objv[i], 15); fprintf(stdout, " "); } fprintf(stdout, "\n"); @@ -1200,40 +1717,117 @@ TclObjInterpProc(clientData, interp, objc, objv) if (TCL_DTRACE_PROC_ARGS_ENABLED()) { char *a[10]; int i = 0; + int l = iPtr->varFramePtr->isProcCallFrame & FRAME_IS_LAMBDA ? 1 : 0; while (i < 10) { - a[i] = i < objc ? TclGetString(objv[i]) : NULL; i++; + a[i] = (l < iPtr->varFramePtr->objc ? + TclGetString(iPtr->varFramePtr->objv[l]) : NULL); i++; l++; } TCL_DTRACE_PROC_ARGS(a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9]); } -#endif /* USE_DTRACE */ + if (TCL_DTRACE_PROC_INFO_ENABLED() && iPtr->cmdFramePtr) { + Tcl_Obj *info = TclInfoFrame(interp, iPtr->cmdFramePtr); + char *a[4]; int i[2]; - iPtr->returnCode = TCL_OK; - procPtr->refCount++; - if (TCL_DTRACE_PROC_ENTRY_ENABLED()) { - TCL_DTRACE_PROC_ENTRY(TclGetString(objv[0]), objc - 1, - (Tcl_Obj **)(objv + 1)); + TclDTraceInfo(info, a, i); + TCL_DTRACE_PROC_INFO(a[0], a[1], a[2], a[3], i[0], i[1]); + TclDecrRefCount(info); } -#ifndef TCL_TIP280 - result = TclCompEvalObj(interp, procPtr->bodyPtr); -#else - /* TIP #280: No need to set the invoking context here. The body has - * already been compiled, so the part of CompEvalObj using it is bypassed. +#endif /* USE_DTRACE */ + + /* + * Invoke the commands in the procedure's body. */ - result = TclCompEvalObj(interp, procPtr->bodyPtr, NULL, 0); -#endif - if (TCL_DTRACE_PROC_RETURN_ENABLED()) { - TCL_DTRACE_PROC_RETURN(TclGetString(objv[0]), result); + procPtr->refCount++; + iPtr->numLevels++; + + if (TclInterpReady(interp) == TCL_ERROR) { + result = TCL_ERROR; + } else { + register ByteCode *codePtr = + procPtr->bodyPtr->internalRep.otherValuePtr; + + codePtr->refCount++; +#ifdef USE_DTRACE + if (TCL_DTRACE_PROC_ENTRY_ENABLED()) { + int l; + + l = iPtr->varFramePtr->isProcCallFrame & FRAME_IS_LAMBDA ? 2 : 1; + TCL_DTRACE_PROC_ENTRY(TclGetString(procNameObj), + iPtr->varFramePtr->objc - l, + (Tcl_Obj **)(iPtr->varFramePtr->objv + l)); + } +#endif /* USE_DTRACE */ + result = TclExecuteByteCode(interp, codePtr); + if (TCL_DTRACE_PROC_RETURN_ENABLED()) { + TCL_DTRACE_PROC_RETURN(TclGetString(procNameObj), result); + } + codePtr->refCount--; + if (codePtr->refCount <= 0) { + TclCleanupByteCode(codePtr); + } } + + iPtr->numLevels--; procPtr->refCount--; if (procPtr->refCount <= 0) { TclProcCleanupProc(procPtr); } - if (result != TCL_OK) { - result = ProcessProcResultCode(interp, procName, nameLen, result); + /* + * Process the result code. + */ + + switch (result) { + case TCL_RETURN: + /* + * If it is a 'return', do the TIP#90 processing now. + */ + + result = TclUpdateReturnInfo((Interp *) interp); + break; + + case TCL_CONTINUE: + case TCL_BREAK: + /* + * It's an error to get to this point from a 'break' or 'continue', so + * transform to an error now. + */ + + Tcl_ResetResult(interp); + Tcl_AppendResult(interp, "invoked \"", + ((result == TCL_BREAK) ? "break" : "continue"), + "\" outside of a loop", NULL); + result = TCL_ERROR; + + /* + * Fall through to the TCL_ERROR handling code. + */ + + case TCL_ERROR: + /* + * Now it _must_ be an error, so we need to log it as such. This means + * filling out the error trace. Luckily, we just hand this off to the + * function handed to us as an argument. + */ + + (*errorProc)(interp, procNameObj); + + default: + /* + * Process other results (OK and non-standard) by doing nothing + * special, skipping directly to the code afterwards that cleans up + * associated memory. + * + * Non-standard results are processed by passing them through quickly. + * This means they all work as exceptions, unwinding the stack quickly + * and neatly. Who knows how well they are handled by third-party code + * though... + */ + + (void) 0; /* do nothing */ } #ifdef USE_DTRACE @@ -1241,23 +1835,26 @@ TclObjInterpProc(clientData, interp, objc, objv) Tcl_Obj *r; r = Tcl_GetObjResult(interp); - TCL_DTRACE_PROC_RESULT(TclGetString(objv[0]), result, + TCL_DTRACE_PROC_RESULT(TclGetString(procNameObj), result, TclGetString(r), r); } #endif /* USE_DTRACE */ + procDone: /* - * Pop and free the call frame for this procedure invocation, then - * free the compiledLocals array if malloc'ed storage was used. + * Free the stack-allocated compiled locals and CallFrame. It is important + * to pop the call frame without freeing it first: the compiledLocals + * cannot be freed before the frame is popped, as the local variables must + * be deleted. But the compiledLocals must be freed first, as they were + * allocated later on the stack. */ - procDone: - Tcl_PopCallFrame(interp); - if (compiledLocals != localStorage) { - ckfree((char *) compiledLocals); - } + freePtr = iPtr->framePtr; + Tcl_PopCallFrame(interp); /* Pop but do not free. */ + TclStackFree(interp, freePtr->compiledLocals); + /* Free compiledLocals. */ + TclStackFree(interp, freePtr); /* Free CallFrame. */ return result; -#undef NUM_LOCALS } /* @@ -1265,150 +1862,149 @@ TclObjInterpProc(clientData, interp, objc, objv) * * TclProcCompileProc -- * - * Called just before a procedure is executed to compile the - * body to byte codes. If the type of the body is not - * "byte code" or if the compile conditions have changed - * (namespace context, epoch counters, etc.) then the body - * is recompiled. Otherwise, this procedure does nothing. + * Called just before a procedure is executed to compile the body to byte + * codes. If the type of the body is not "byte code" or if the compile + * conditions have changed (namespace context, epoch counters, etc.) then + * the body is recompiled. Otherwise, this function does nothing. * * Results: * None. * * Side effects: - * May change the internal representation of the body object - * to compiled code. + * May change the internal representation of the body object to compiled + * code. * *---------------------------------------------------------------------- */ int -TclProcCompileProc(interp, procPtr, bodyPtr, nsPtr, description, procName) - Tcl_Interp *interp; /* Interpreter containing procedure. */ - Proc *procPtr; /* Data associated with procedure. */ - Tcl_Obj *bodyPtr; /* Body of proc. (Usually procPtr->bodyPtr, - * but could be any code fragment compiled - * in the context of this procedure.) */ - Namespace *nsPtr; /* Namespace containing procedure. */ - CONST char *description; /* string describing this body of code. */ - CONST char *procName; /* Name of this procedure. */ +TclProcCompileProc( + Tcl_Interp *interp, /* Interpreter containing procedure. */ + Proc *procPtr, /* Data associated with procedure. */ + Tcl_Obj *bodyPtr, /* Body of proc. (Usually procPtr->bodyPtr, + * but could be any code fragment compiled in + * the context of this procedure.) */ + Namespace *nsPtr, /* Namespace containing procedure. */ + CONST char *description, /* string describing this body of code. */ + CONST char *procName) /* Name of this procedure. */ { - return ProcCompileProc(interp, procPtr, bodyPtr, nsPtr, - description, procName, NULL); + return ProcCompileProc(interp, procPtr, bodyPtr, nsPtr, description, + procName, NULL); } static int -ProcCompileProc(interp, procPtr, bodyPtr, nsPtr, description, - procName, procPtrPtr) - Tcl_Interp *interp; /* Interpreter containing procedure. */ - Proc *procPtr; /* Data associated with procedure. */ - Tcl_Obj *bodyPtr; /* Body of proc. (Usually procPtr->bodyPtr, - * but could be any code fragment compiled - * in the context of this procedure.) */ - Namespace *nsPtr; /* Namespace containing procedure. */ - CONST char *description; /* string describing this body of code. */ - CONST char *procName; /* Name of this procedure. */ - Proc **procPtrPtr; /* points to storage where a replacement - * (Proc *) value may be written, when - * appropriate */ +ProcCompileProc( + Tcl_Interp *interp, /* Interpreter containing procedure. */ + Proc *procPtr, /* Data associated with procedure. */ + Tcl_Obj *bodyPtr, /* Body of proc. (Usually procPtr->bodyPtr, + * but could be any code fragment compiled in + * the context of this procedure.) */ + Namespace *nsPtr, /* Namespace containing procedure. */ + CONST char *description, /* string describing this body of code. */ + CONST char *procName, /* Name of this procedure. */ + Proc **procPtrPtr) /* Points to storage where a replacement + * (Proc *) value may be written. */ { - Interp *iPtr = (Interp*)interp; - int i, result; - Tcl_CallFrame frame; - ByteCode *codePtr = (ByteCode *) bodyPtr->internalRep.otherValuePtr; + Interp *iPtr = (Interp *) interp; + int i; + Tcl_CallFrame *framePtr; + ByteCode *codePtr = bodyPtr->internalRep.otherValuePtr; CompiledLocal *localPtr; /* - * If necessary, compile the procedure's body. The compiler will - * allocate frame slots for the procedure's non-argument local - * variables. If the ByteCode already exists, make sure it hasn't been - * invalidated by someone redefining a core command (this might make the - * compiled code wrong). Also, if the code was compiled in/for a - * different interpreter, we recompile it. Note that compiling the body - * might increase procPtr->numCompiledLocals if new local variables are - * found while compiling. + * If necessary, compile the procedure's body. The compiler will allocate + * frame slots for the procedure's non-argument local variables. If the + * ByteCode already exists, make sure it hasn't been invalidated by + * someone redefining a core command (this might make the compiled code + * wrong). Also, if the code was compiled in/for a different interpreter, + * we recompile it. Note that compiling the body might increase + * procPtr->numCompiledLocals if new local variables are found while + * compiling. * - * Precompiled procedure bodies, however, are immutable and therefore - * they are not recompiled, even if things have changed. + * Precompiled procedure bodies, however, are immutable and therefore they + * are not recompiled, even if things have changed. */ if (bodyPtr->typePtr == &tclByteCodeType) { - if (((Interp *) *codePtr->interpHandle != iPtr) - || (codePtr->compileEpoch != iPtr->compileEpoch) - || (codePtr->nsPtr != nsPtr)) { - if (codePtr->flags & TCL_BYTECODE_PRECOMPILED) { - if ((Interp *) *codePtr->interpHandle != iPtr) { - Tcl_AppendResult(interp, - "a precompiled script jumped interps", NULL); - return TCL_ERROR; - } - codePtr->compileEpoch = iPtr->compileEpoch; - codePtr->nsPtr = nsPtr; - } else { - (*tclByteCodeType.freeIntRepProc)(bodyPtr); - bodyPtr->typePtr = (Tcl_ObjType *) NULL; - } + if (((Interp *) *codePtr->interpHandle == iPtr) + && (codePtr->compileEpoch == iPtr->compileEpoch) + && (codePtr->nsPtr == nsPtr) + && (codePtr->nsEpoch == nsPtr->resolverEpoch)) { + return TCL_OK; + } else { + if (codePtr->flags & TCL_BYTECODE_PRECOMPILED) { + if ((Interp *) *codePtr->interpHandle != iPtr) { + Tcl_AppendResult(interp, + "a precompiled script jumped interps", NULL); + return TCL_ERROR; + } + codePtr->compileEpoch = iPtr->compileEpoch; + codePtr->nsPtr = nsPtr; + } else { + bodyPtr->typePtr->freeIntRepProc(bodyPtr); + bodyPtr->typePtr = NULL; + } } } if (bodyPtr->typePtr != &tclByteCodeType) { - int numChars; - char *ellipsis; + Tcl_HashEntry *hePtr; #ifdef TCL_COMPILE_DEBUG if (tclTraceCompile >= 1) { /* - * Display a line summarizing the top level command we - * are about to compile. + * Display a line summarizing the top level command we are about + * to compile. */ - numChars = strlen(procName); - ellipsis = ""; - if (numChars > 50) { - numChars = 50; - ellipsis = "..."; - } - fprintf(stdout, "Compiling %s \"%.*s%s\"\n", - description, numChars, procName, ellipsis); + Tcl_Obj *message; + + TclNewLiteralStringObj(message, "Compiling "); + Tcl_IncrRefCount(message); + Tcl_AppendStringsToObj(message, description, " \"", NULL); + Tcl_AppendLimitedToObj(message, procName, -1, 50, NULL); + fprintf(stdout, "%s\"\n", TclGetString(message)); + Tcl_DecrRefCount(message); } #endif /* - * Plug the current procPtr into the interpreter and coerce - * the code body to byte codes. The interpreter needs to - * know which proc it's compiling so that it can access its - * list of compiled locals. + * Plug the current procPtr into the interpreter and coerce the code + * body to byte codes. The interpreter needs to know which proc it's + * compiling so that it can access its list of compiled locals. * - * TRICKY NOTE: Be careful to push a call frame with the - * proper namespace context, so that the byte codes are - * compiled in the appropriate class context. + * TRICKY NOTE: Be careful to push a call frame with the proper + * namespace context, so that the byte codes are compiled in the + * appropriate class context. */ if (procPtrPtr != NULL && procPtr->refCount > 1) { Tcl_Command token; Tcl_CmdInfo info; - Proc *new = (Proc *) ckalloc(sizeof(Proc)); - - new->iPtr = procPtr->iPtr; - new->refCount = 1; - new->cmdPtr = procPtr->cmdPtr; - token = (Tcl_Command) new->cmdPtr; - new->bodyPtr = Tcl_DuplicateObj(bodyPtr); - bodyPtr = new->bodyPtr; + Proc *newProc = (Proc *) ckalloc(sizeof(Proc)); + + newProc->iPtr = procPtr->iPtr; + newProc->refCount = 1; + newProc->cmdPtr = procPtr->cmdPtr; + token = (Tcl_Command) newProc->cmdPtr; + newProc->bodyPtr = Tcl_DuplicateObj(bodyPtr); + bodyPtr = newProc->bodyPtr; Tcl_IncrRefCount(bodyPtr); - new->numArgs = procPtr->numArgs; + newProc->numArgs = procPtr->numArgs; - new->numCompiledLocals = new->numArgs; - new->firstLocalPtr = NULL; - new->lastLocalPtr = NULL; + newProc->numCompiledLocals = newProc->numArgs; + newProc->firstLocalPtr = NULL; + newProc->lastLocalPtr = NULL; localPtr = procPtr->firstLocalPtr; - for (i = 0; i < new->numArgs; i++, localPtr = localPtr->nextPtr) { + for (i=0; i<newProc->numArgs; i++, localPtr=localPtr->nextPtr) { CompiledLocal *copy = (CompiledLocal *) ckalloc((unsigned) - (sizeof(CompiledLocal) -sizeof(localPtr->name) - + localPtr->nameLength + 1)); - if (new->firstLocalPtr == NULL) { - new->firstLocalPtr = new->lastLocalPtr = copy; + (sizeof(CompiledLocal) - sizeof(localPtr->name) + + localPtr->nameLength + 1)); + + if (newProc->firstLocalPtr == NULL) { + newProc->firstLocalPtr = newProc->lastLocalPtr = copy; } else { - new->lastLocalPtr->nextPtr = copy; - new->lastLocalPtr = copy; + newProc->lastLocalPtr->nextPtr = copy; + newProc->lastLocalPtr = copy; } copy->nextPtr = NULL; copy->nameLength = localPtr->nameLength; @@ -1419,97 +2015,58 @@ ProcCompileProc(interp, procPtr, bodyPtr, nsPtr, description, Tcl_IncrRefCount(copy->defValuePtr); } copy->resolveInfo = localPtr->resolveInfo; - memcpy(copy->name, localPtr->name, localPtr->nameLength + 1); + memcpy(copy->name, localPtr->name, localPtr->nameLength + 1); } + /* + * Reset the ClientData + */ - /* Reset the ClientData */ Tcl_GetCommandInfoFromToken(token, &info); if (info.objClientData == (ClientData) procPtr) { - info.objClientData = (ClientData) new; + info.objClientData = (ClientData) newProc; } if (info.clientData == (ClientData) procPtr) { - info.clientData = (ClientData) new; + info.clientData = (ClientData) newProc; } if (info.deleteData == (ClientData) procPtr) { - info.deleteData = (ClientData) new; + info.deleteData = (ClientData) newProc; } Tcl_SetCommandInfoFromToken(token, &info); procPtr->refCount--; - *procPtrPtr = procPtr = new; + *procPtrPtr = procPtr = newProc; } iPtr->compiledProcPtr = procPtr; - result = Tcl_PushCallFrame(interp, &frame, - (Tcl_Namespace*)nsPtr, /* isProcCallFrame */ 0); + (void) TclPushStackFrame(interp, &framePtr, + (Tcl_Namespace *) nsPtr, /* isProcCallFrame */ 0); - if (result == TCL_OK) { -#ifdef TCL_TIP280 - /* TIP #280. We get the invoking context from the cmdFrame - * which was saved by 'Tcl_ProcObjCmd' (using linePBodyPtr). - */ + /* + * TIP #280: We get the invoking context from the cmdFrame which + * was saved by 'Tcl_ProcObjCmd' (using linePBodyPtr). + */ - Tcl_HashEntry* hePtr = Tcl_FindHashEntry (iPtr->linePBodyPtr, (char *) procPtr); + hePtr = Tcl_FindHashEntry(iPtr->linePBodyPtr, (char *) procPtr); - /* Constructed saved frame has body as word 0. See Tcl_ProcObjCmd. - */ - iPtr->invokeWord = 0; - iPtr->invokeCmdFramePtr = (hePtr - ? (CmdFrame*) Tcl_GetHashValue (hePtr) - : NULL); -#endif - result = tclByteCodeType.setFromAnyProc(interp, bodyPtr); -#ifdef TCL_TIP280 - iPtr->invokeCmdFramePtr = NULL; -#endif - Tcl_PopCallFrame(interp); - } + /* + * Constructed saved frame has body as word 0. See Tcl_ProcObjCmd. + */ - if (result != TCL_OK) { - if (result == TCL_ERROR) { - char buf[100 + TCL_INTEGER_SPACE]; - - numChars = strlen(procName); - ellipsis = ""; - if (numChars > 50) { - numChars = 50; - ellipsis = "..."; - } - while ( (procName[numChars] & 0xC0) == 0x80 ) { - /* - * Back up truncation point so that we don't truncate - * in the middle of a multi-byte character (in UTF-8) - */ - numChars--; - ellipsis = "..."; - } - sprintf(buf, "\n (compiling %s \"%.*s%s\", line %d)", - description, numChars, procName, ellipsis, - interp->errorLine); - Tcl_AddObjErrorInfo(interp, buf, -1); - } - return result; - } + iPtr->invokeWord = 0; + iPtr->invokeCmdFramePtr = + (hePtr ? (CmdFrame *) Tcl_GetHashValue(hePtr) : NULL); + (void) tclByteCodeType.setFromAnyProc(interp, bodyPtr); + iPtr->invokeCmdFramePtr = NULL; + TclPopStackFrame(interp); } else if (codePtr->nsEpoch != nsPtr->resolverEpoch) { - /* - * The resolver epoch has changed, but we only need to invalidate - * the resolver cache. + * The resolver epoch has changed, but we only need to invalidate the + * resolver cache. */ - for (localPtr = procPtr->firstLocalPtr; localPtr != NULL; - localPtr = localPtr->nextPtr) { - localPtr->flags &= ~(VAR_RESOLVED); - if (localPtr->resolveInfo) { - if (localPtr->resolveInfo->deleteProc) { - localPtr->resolveInfo->deleteProc(localPtr->resolveInfo); - } else { - ckfree((char*)localPtr->resolveInfo); - } - localPtr->resolveInfo = NULL; - } - } + codePtr->nsEpoch = nsPtr->resolverEpoch; + codePtr->flags |= TCL_BYTECODE_RESOLVE_VARS; } return TCL_OK; } @@ -1517,68 +2074,36 @@ ProcCompileProc(interp, procPtr, bodyPtr, nsPtr, description, /* *---------------------------------------------------------------------- * - * ProcessProcResultCode -- + * MakeProcError -- * - * Procedure called by TclObjInterpProc to process a return code other - * than TCL_OK returned by a Tcl procedure. + * Function called by TclObjInterpProc to create the stack information + * upon an error from a procedure. * * Results: - * Depending on the argument return code, the result returned is - * another return code and the interpreter's result is set to a value - * to supplement that return code. + * The interpreter's error info trace is set to a value that supplements + * the error code. * * Side effects: - * If the result returned is TCL_ERROR, traceback information about - * the procedure just executed is appended to the interpreter's - * "errorInfo" variable. + * none. * *---------------------------------------------------------------------- */ -static int -ProcessProcResultCode(interp, procName, nameLen, returnCode) - Tcl_Interp *interp; /* The interpreter in which the procedure - * was called and returned returnCode. */ - char *procName; /* Name of the procedure. Used for error +static void +MakeProcError( + Tcl_Interp *interp, /* The interpreter in which the procedure was + * called. */ + Tcl_Obj *procNameObj) /* Name of the procedure. Used for error * messages and trace information. */ - int nameLen; /* Number of bytes in procedure's name. */ - int returnCode; /* The unexpected result code. */ { - Interp *iPtr = (Interp *) interp; - char msg[100 + TCL_INTEGER_SPACE]; - char *ellipsis = ""; - - if (returnCode == TCL_OK) { - return TCL_OK; - } - if ((returnCode > TCL_CONTINUE) || (returnCode < TCL_OK)) { - return returnCode; - } - if (returnCode == TCL_RETURN) { - return TclUpdateReturnInfo(iPtr); - } - if (returnCode != TCL_ERROR) { - Tcl_ResetResult(interp); - Tcl_AppendToObj(Tcl_GetObjResult(interp), ((returnCode == TCL_BREAK) - ? "invoked \"break\" outside of a loop" - : "invoked \"continue\" outside of a loop"), -1); - } - if (nameLen > 60) { - nameLen = 60; - ellipsis = "..."; - } - while ( (procName[nameLen] & 0xC0) == 0x80 ) { - /* - * Back up truncation point so that we don't truncate in the - * middle of a multi-byte character (in UTF-8) - */ - nameLen--; - ellipsis = "..."; - } - sprintf(msg, "\n (procedure \"%.*s%s\" line %d)", nameLen, procName, - ellipsis, iPtr->errorLine); - Tcl_AddObjErrorInfo(interp, msg, -1); - return TCL_ERROR; + int overflow, limit = 60, nameLen; + const char *procName = Tcl_GetStringFromObj(procNameObj, &nameLen); + + overflow = (nameLen > limit); + Tcl_AppendObjToErrorInfo(interp, Tcl_ObjPrintf( + "\n (procedure \"%.*s%s\" line %d)", + (overflow ? limit : nameLen), procName, + (overflow ? "..." : ""), interp->errorLine)); } /* @@ -1586,24 +2111,24 @@ ProcessProcResultCode(interp, procName, nameLen, returnCode) * * TclProcDeleteProc -- * - * This procedure is invoked just before a command procedure is - * removed from an interpreter. Its job is to release all the - * resources allocated to the procedure. + * This function is invoked just before a command procedure is removed + * from an interpreter. Its job is to release all the resources allocated + * to the procedure. * * Results: * None. * * Side effects: - * Memory gets freed, unless the procedure is actively being - * executed. In this case the cleanup is delayed until the - * last call to the current procedure completes. + * Memory gets freed, unless the procedure is actively being executed. + * In this case the cleanup is delayed until the last call to the current + * procedure completes. * *---------------------------------------------------------------------- */ void -TclProcDeleteProc(clientData) - ClientData clientData; /* Procedure to be deleted. */ +TclProcDeleteProc( + ClientData clientData) /* Procedure to be deleted. */ { Proc *procPtr = (Proc *) clientData; @@ -1618,9 +2143,8 @@ TclProcDeleteProc(clientData) * * TclProcCleanupProc -- * - * This procedure does all the real work of freeing up a Proc - * structure. It's called only when the structure's reference - * count becomes zero. + * This function does all the real work of freeing up a Proc structure. + * It's called only when the structure's reference count becomes zero. * * Results: * None. @@ -1632,33 +2156,31 @@ TclProcDeleteProc(clientData) */ void -TclProcCleanupProc(procPtr) - register Proc *procPtr; /* Procedure to be deleted. */ +TclProcCleanupProc( + register Proc *procPtr) /* Procedure to be deleted. */ { register CompiledLocal *localPtr; Tcl_Obj *bodyPtr = procPtr->bodyPtr; Tcl_Obj *defPtr; Tcl_ResolvedVarInfo *resVarInfo; -#ifdef TCL_TIP280 - Tcl_HashEntry* hePtr = NULL; - CmdFrame* cfPtr = NULL; - Interp* iPtr = procPtr->iPtr; -#endif + Tcl_HashEntry *hePtr = NULL; + CmdFrame *cfPtr = NULL; + Interp *iPtr = procPtr->iPtr; if (bodyPtr != NULL) { Tcl_DecrRefCount(bodyPtr); } - for (localPtr = procPtr->firstLocalPtr; localPtr != NULL; ) { + for (localPtr = procPtr->firstLocalPtr; localPtr != NULL; ) { CompiledLocal *nextPtr = localPtr->nextPtr; - resVarInfo = localPtr->resolveInfo; + resVarInfo = localPtr->resolveInfo; if (resVarInfo) { if (resVarInfo->deleteProc) { (*resVarInfo->deleteProc)(resVarInfo); } else { ckfree((char *) resVarInfo); } - } + } if (localPtr->defValuePtr != NULL) { defPtr = localPtr->defValuePtr; @@ -1669,27 +2191,32 @@ TclProcCleanupProc(procPtr) } ckfree((char *) procPtr); -#ifdef TCL_TIP280 - /* TIP #280. Release the location data associated with this Proc + /* + * TIP #280: Release the location data associated with this Proc * structure, if any. The interpreter may not exist (For example for - * procbody structurues created by tbcload. + * procbody structures created by tbcload. See also Tcl_ProcObjCmd(), when + * the same ProcPtr is overwritten with a new CmdFrame. */ - if (!iPtr) return; + if (!iPtr) { + return; + } - hePtr = Tcl_FindHashEntry (iPtr->linePBodyPtr, (char *) procPtr); - if (!hePtr) return; + hePtr = Tcl_FindHashEntry(iPtr->linePBodyPtr, (char *) procPtr); + if (!hePtr) { + return; + } - cfPtr = (CmdFrame*) Tcl_GetHashValue (hePtr); + cfPtr = (CmdFrame *) Tcl_GetHashValue(hePtr); if (cfPtr->type == TCL_LOCATION_SOURCE) { - Tcl_DecrRefCount (cfPtr->data.eval.path); + Tcl_DecrRefCount(cfPtr->data.eval.path); cfPtr->data.eval.path = NULL; } - ckfree ((char*) cfPtr->line); cfPtr->line = NULL; - ckfree ((char*) cfPtr); - Tcl_DeleteHashEntry (hePtr); -#endif + ckfree((char *) cfPtr->line); + cfPtr->line = NULL; + ckfree((char *) cfPtr); + Tcl_DeleteHashEntry(hePtr); } /* @@ -1697,47 +2224,44 @@ TclProcCleanupProc(procPtr) * * TclUpdateReturnInfo -- * - * This procedure is called when procedures return, and at other - * points where the TCL_RETURN code is used. It examines fields - * such as iPtr->returnCode and iPtr->errorCode and modifies - * the real return status accordingly. + * This function is called when procedures return, and at other points + * where the TCL_RETURN code is used. It examines the returnLevel and + * returnCode to determine the real return status. * * Results: - * The return value is the true completion code to use for - * the procedure, instead of TCL_RETURN. + * The return value is the true completion code to use for the procedure + * or script, instead of TCL_RETURN. * * Side effects: - * The errorInfo and errorCode variables may get modified. + * None. * *---------------------------------------------------------------------- */ int -TclUpdateReturnInfo(iPtr) - Interp *iPtr; /* Interpreter for which TCL_RETURN - * exception is being processed. */ +TclUpdateReturnInfo( + Interp *iPtr) /* Interpreter for which TCL_RETURN exception + * is being processed. */ { - int code; - char *errorCode; - Tcl_Obj *objPtr; + int code = TCL_RETURN; - code = iPtr->returnCode; - iPtr->returnCode = TCL_OK; - if (code == TCL_ERROR) { - errorCode = ((iPtr->errorCode != NULL) ? iPtr->errorCode : "NONE"); - objPtr = Tcl_NewStringObj(errorCode, -1); - Tcl_IncrRefCount(objPtr); - Tcl_ObjSetVar2((Tcl_Interp *) iPtr, iPtr->execEnvPtr->errorCode, - NULL, objPtr, TCL_GLOBAL_ONLY); - Tcl_DecrRefCount(objPtr); - iPtr->flags |= ERROR_CODE_SET; - if (iPtr->errorInfo != NULL) { - objPtr = Tcl_NewStringObj(iPtr->errorInfo, -1); - Tcl_IncrRefCount(objPtr); - Tcl_ObjSetVar2((Tcl_Interp *) iPtr, iPtr->execEnvPtr->errorInfo, - NULL, objPtr, TCL_GLOBAL_ONLY); - Tcl_DecrRefCount(objPtr); - iPtr->flags |= ERR_IN_PROGRESS; + iPtr->returnLevel--; + if (iPtr->returnLevel < 0) { + Tcl_Panic("TclUpdateReturnInfo: negative return level"); + } + if (iPtr->returnLevel == 0) { + /* + * Now we've reached the level to return the requested -code. + * Since iPtr->returnLevel and iPtr->returnCode have completed + * their task, we now reset them to default values so that any + * bare "return TCL_RETURN" that may follow will work [Bug 2152286]. + */ + + code = iPtr->returnCode; + iPtr->returnLevel = 1; + iPtr->returnCode = TCL_OK; + if (code == TCL_ERROR) { + iPtr->flags |= ERR_LEGACY_COPY; } } return code; @@ -1746,49 +2270,24 @@ TclUpdateReturnInfo(iPtr) /* *---------------------------------------------------------------------- * - * TclGetInterpProc -- - * - * Returns a pointer to the TclProcInterpProc procedure; this is different - * from the value obtained from the TclProcInterpProc reference on systems - * like Windows where import and export versions of a procedure exported - * by a DLL exist. - * - * Results: - * Returns the internal address of the TclProcInterpProc procedure. - * - * Side effects: - * None. - * - *---------------------------------------------------------------------- - */ - -TclCmdProcType -TclGetInterpProc() -{ - return (TclCmdProcType) TclProcInterpProc; -} - -/* - *---------------------------------------------------------------------- - * * TclGetObjInterpProc -- * - * Returns a pointer to the TclObjInterpProc procedure; this is different - * from the value obtained from the TclObjInterpProc reference on systems - * like Windows where import and export versions of a procedure exported - * by a DLL exist. + * Returns a pointer to the TclObjInterpProc function; this is different + * from the value obtained from the TclObjInterpProc reference on systems + * like Windows where import and export versions of a function exported + * by a DLL exist. * * Results: - * Returns the internal address of the TclObjInterpProc procedure. + * Returns the internal address of the TclObjInterpProc function. * * Side effects: - * None. + * None. * *---------------------------------------------------------------------- */ TclObjCmdProcType -TclGetObjInterpProc() +TclGetObjInterpProc(void) { return (TclObjCmdProcType) TclObjInterpProc; } @@ -1798,38 +2297,37 @@ TclGetObjInterpProc() * * TclNewProcBodyObj -- * - * Creates a new object, of type "procbody", whose internal - * representation is the given Proc struct. - * The newly created object's reference count is 0. + * Creates a new object, of type "procbody", whose internal + * representation is the given Proc struct. The newly created object's + * reference count is 0. * * Results: - * Returns a pointer to a newly allocated Tcl_Obj, 0 on error. + * Returns a pointer to a newly allocated Tcl_Obj, NULL on error. * * Side effects: - * The reference count in the ByteCode attached to the Proc is bumped up - * by one, since the internal rep stores a pointer to it. + * The reference count in the ByteCode attached to the Proc is bumped up + * by one, since the internal rep stores a pointer to it. * *---------------------------------------------------------------------- */ Tcl_Obj * -TclNewProcBodyObj(procPtr) - Proc *procPtr; /* the Proc struct to store as the internal - * representation. */ +TclNewProcBodyObj( + Proc *procPtr) /* the Proc struct to store as the internal + * representation. */ { Tcl_Obj *objPtr; if (!procPtr) { - return (Tcl_Obj *) NULL; + return NULL; } - objPtr = Tcl_NewStringObj("", 0); - + TclNewObj(objPtr); if (objPtr) { - objPtr->typePtr = &tclProcBodyType; - objPtr->internalRep.otherValuePtr = (VOID *) procPtr; + objPtr->typePtr = &tclProcBodyType; + objPtr->internalRep.otherValuePtr = procPtr; - procPtr->refCount++; + procPtr->refCount++; } return objPtr; @@ -1840,27 +2338,27 @@ TclNewProcBodyObj(procPtr) * * ProcBodyDup -- * - * Tcl_ObjType's Dup function for the proc body object. - * Bumps the reference count on the Proc stored in the internal - * representation. + * Tcl_ObjType's Dup function for the proc body object. Bumps the + * reference count on the Proc stored in the internal representation. * * Results: - * None. + * None. * * Side effects: - * Sets up the object in dupPtr to be a duplicate of the one in srcPtr. + * Sets up the object in dupPtr to be a duplicate of the one in srcPtr. * *---------------------------------------------------------------------- */ -static void ProcBodyDup(srcPtr, dupPtr) - Tcl_Obj *srcPtr; /* object to copy */ - Tcl_Obj *dupPtr; /* target object for the duplication */ +static void +ProcBodyDup( + Tcl_Obj *srcPtr, /* Object to copy. */ + Tcl_Obj *dupPtr) /* Target object for the duplication. */ { - Proc *procPtr = (Proc *) srcPtr->internalRep.otherValuePtr; + Proc *procPtr = srcPtr->internalRep.otherValuePtr; dupPtr->typePtr = &tclProcBodyType; - dupPtr->internalRep.otherValuePtr = (VOID *) procPtr; + dupPtr->internalRep.otherValuePtr = procPtr; procPtr->refCount++; } @@ -1869,130 +2367,540 @@ static void ProcBodyDup(srcPtr, dupPtr) * * ProcBodyFree -- * - * Tcl_ObjType's Free function for the proc body object. - * The reference count on its Proc struct is decreased by 1; if the count - * reaches 0, the proc is freed. + * Tcl_ObjType's Free function for the proc body object. The reference + * count on its Proc struct is decreased by 1; if the count reaches 0, + * the proc is freed. * * Results: - * None. + * None. * * Side effects: - * If the reference count on the Proc struct reaches 0, the struct is freed. + * If the reference count on the Proc struct reaches 0, the struct is + * freed. * *---------------------------------------------------------------------- */ static void -ProcBodyFree(objPtr) - Tcl_Obj *objPtr; /* the object to clean up */ +ProcBodyFree( + Tcl_Obj *objPtr) /* The object to clean up. */ { - Proc *procPtr = (Proc *) objPtr->internalRep.otherValuePtr; + Proc *procPtr = objPtr->internalRep.otherValuePtr; + procPtr->refCount--; if (procPtr->refCount <= 0) { - TclProcCleanupProc(procPtr); + TclProcCleanupProc(procPtr); } } /* *---------------------------------------------------------------------- * - * ProcBodySetFromAny -- - * - * Tcl_ObjType's SetFromAny function for the proc body object. - * Calls panic. - * - * Results: - * Theoretically returns a TCL result code. + * DupLambdaInternalRep, FreeLambdaInternalRep, SetLambdaFromAny -- * - * Side effects: - * Calls panic, since we can't set the value of the object from a string - * representation (or any other internal ones). + * How to manage the internal representations of lambda term objects. + * Syntactically they look like a two- or three-element list, where the + * first element is the formal arguments, the second is the the body, and + * the (optional) third is the namespace to execute the lambda term + * within (the global namespace is assumed if it is absent). * *---------------------------------------------------------------------- */ +static void +DupLambdaInternalRep( + Tcl_Obj *srcPtr, /* Object with internal rep to copy. */ + register Tcl_Obj *copyPtr) /* Object with internal rep to set. */ +{ + Proc *procPtr = srcPtr->internalRep.twoPtrValue.ptr1; + Tcl_Obj *nsObjPtr = srcPtr->internalRep.twoPtrValue.ptr2; + + copyPtr->internalRep.twoPtrValue.ptr1 = procPtr; + copyPtr->internalRep.twoPtrValue.ptr2 = nsObjPtr; + + procPtr->refCount++; + Tcl_IncrRefCount(nsObjPtr); + copyPtr->typePtr = &lambdaType; +} + +static void +FreeLambdaInternalRep( + register Tcl_Obj *objPtr) /* CmdName object with internal representation + * to free. */ +{ + Proc *procPtr = objPtr->internalRep.twoPtrValue.ptr1; + Tcl_Obj *nsObjPtr = objPtr->internalRep.twoPtrValue.ptr2; + + procPtr->refCount--; + if (procPtr->refCount == 0) { + TclProcCleanupProc(procPtr); + } + TclDecrRefCount(nsObjPtr); + objPtr->typePtr = NULL; +} + static int -ProcBodySetFromAny(interp, objPtr) - Tcl_Interp *interp; /* current interpreter */ - Tcl_Obj *objPtr; /* object pointer */ +SetLambdaFromAny( + Tcl_Interp *interp, /* Used for error reporting if not NULL. */ + register Tcl_Obj *objPtr) /* The object to convert. */ { - panic("called ProcBodySetFromAny"); + Interp *iPtr = (Interp *) interp; + char *name; + Tcl_Obj *argsPtr, *bodyPtr, *nsObjPtr, **objv, *errPtr; + int objc, result; + Proc *procPtr; + + if (interp == NULL) { + return TCL_ERROR; + } + + /* + * Convert objPtr to list type first; if it cannot be converted, or if its + * length is not 2, then it cannot be converted to lambdaType. + */ + + result = TclListObjGetElements(NULL, objPtr, &objc, &objv); + if ((result != TCL_OK) || ((objc != 2) && (objc != 3))) { + TclNewLiteralStringObj(errPtr, "can't interpret \""); + Tcl_AppendObjToObj(errPtr, objPtr); + Tcl_AppendToObj(errPtr, "\" as a lambda expression", -1); + Tcl_SetObjResult(interp, errPtr); + return TCL_ERROR; + } + + argsPtr = objv[0]; + bodyPtr = objv[1]; + + /* + * Create and initialize the Proc struct. The cmdPtr field is set to NULL + * to signal that this is an anonymous function. + */ + + name = TclGetString(objPtr); + + if (TclCreateProc(interp, /*ignored nsPtr*/ NULL, name, argsPtr, bodyPtr, + &procPtr) != TCL_OK) { + Tcl_AppendObjToErrorInfo(interp, Tcl_ObjPrintf( + "\n (parsing lambda expression \"%s\")", name)); + return TCL_ERROR; + } + + /* + * CAREFUL: TclCreateProc returns refCount==1! [Bug 1578454] + * procPtr->refCount = 1; + */ + + procPtr->cmdPtr = NULL; + + /* + * TIP #280: Remember the line the apply body is starting on. In a Byte + * code context we ask the engine to provide us with the necessary + * information. This is for the initialization of the byte code compiler + * when the body is used for the first time. + * + * NOTE: The body is the second word in the 'objPtr'. Its location, + * accessible through 'context.line[1]' (see below) is therefore only the + * first approximation of the actual line the body is on. We have to use + * the string rep of the 'objPtr' to determine the exact line. This is + * available already through 'name'. Use 'TclListLines', see 'switch' + * (tclCmdMZ.c). + * + * This code is nearly identical to the #280 code in Tcl_ProcObjCmd, see + * this file. The differences are the different index of the body in the + * line array of the context, and the special processing mentioned in the + * previous paragraph to track into the list. Find a way to factor the + * common elements into a single function. + */ + + if (iPtr->cmdFramePtr) { + CmdFrame *contextPtr; + + contextPtr = (CmdFrame *) TclStackAlloc(interp, sizeof(CmdFrame)); + *contextPtr = *iPtr->cmdFramePtr; + + if (contextPtr->type == TCL_LOCATION_BC) { + /* + * Retrieve the source context from the bytecode. This call + * accounts for the reference to the source file, if any, held in + * 'context.data.eval.path'. + */ + + TclGetSrcInfoForPc(contextPtr); + } else if (contextPtr->type == TCL_LOCATION_SOURCE) { + /* + * We created a new reference to the source file path name when we + * created 'context' above. Account for the reference. + */ + + Tcl_IncrRefCount(contextPtr->data.eval.path); + + } + + if (contextPtr->type == TCL_LOCATION_SOURCE) { + /* + * We can record source location within a lambda only if the body + * was not created by substitution. + */ + + if (contextPtr->line + && (contextPtr->nline >= 2) && (contextPtr->line[1] >= 0)) { + int isNew, buf[2]; + CmdFrame *cfPtr = (CmdFrame *) ckalloc(sizeof(CmdFrame)); + + /* + * Move from approximation (line of list cmd word) to actual + * location (line of 2nd list element). + */ + + TclListLines(objPtr, contextPtr->line[1], 2, buf, NULL); + + cfPtr->level = -1; + cfPtr->type = contextPtr->type; + cfPtr->line = (int *) ckalloc(sizeof(int)); + cfPtr->line[0] = buf[1]; + cfPtr->nline = 1; + cfPtr->framePtr = NULL; + cfPtr->nextPtr = NULL; + + cfPtr->data.eval.path = contextPtr->data.eval.path; + Tcl_IncrRefCount(cfPtr->data.eval.path); + + cfPtr->cmd.str.cmd = NULL; + cfPtr->cmd.str.len = 0; + + Tcl_SetHashValue(Tcl_CreateHashEntry(iPtr->linePBodyPtr, + (char *) procPtr, &isNew), cfPtr); + } + + /* + * 'contextPtr' is going out of scope. Release the reference that + * it's holding to the source file path + */ + + Tcl_DecrRefCount(contextPtr->data.eval.path); + } + TclStackFree(interp, contextPtr); + } + + /* + * Set the namespace for this lambda: given by objv[2] understood as a + * global reference, or else global per default. + */ + + if (objc == 2) { + TclNewLiteralStringObj(nsObjPtr, "::"); + } else { + char *nsName = TclGetString(objv[2]); + + if ((*nsName != ':') || (*(nsName+1) != ':')) { + TclNewLiteralStringObj(nsObjPtr, "::"); + Tcl_AppendObjToObj(nsObjPtr, objv[2]); + } else { + nsObjPtr = objv[2]; + } + } + + Tcl_IncrRefCount(nsObjPtr); /* - * this to keep compilers happy. + * Free the list internalrep of objPtr - this will free argsPtr, but + * bodyPtr retains a reference from the Proc structure. Then finish the + * conversion to lambdaType. */ + objPtr->typePtr->freeIntRepProc(objPtr); + + objPtr->internalRep.twoPtrValue.ptr1 = procPtr; + objPtr->internalRep.twoPtrValue.ptr2 = nsObjPtr; + objPtr->typePtr = &lambdaType; return TCL_OK; } /* *---------------------------------------------------------------------- * - * ProcBodyUpdateString -- + * Tcl_ApplyObjCmd -- * - * Tcl_ObjType's UpdateString function for the proc body object. - * Calls panic. + * This object-based function is invoked to process the "apply" Tcl + * command. See the user documentation for details on what it does. * * Results: - * None. + * A standard Tcl object result value. * * Side effects: - * Calls panic, since we this type has no string representation. + * Depends on the content of the lambda term (i.e., objv[1]). * *---------------------------------------------------------------------- */ -static void -ProcBodyUpdateString(objPtr) - Tcl_Obj *objPtr; /* the object to update */ +int +Tcl_ApplyObjCmd( + ClientData dummy, /* Not used. */ + Tcl_Interp *interp, /* Current interpreter. */ + int objc, /* Number of arguments. */ + Tcl_Obj *CONST objv[]) /* Argument objects. */ { - panic("called ProcBodyUpdateString"); -} + Interp *iPtr = (Interp *) interp; + Proc *procPtr = NULL; + Tcl_Obj *lambdaPtr, *nsObjPtr; + int result, isRootEnsemble; + Command cmd; + Tcl_Namespace *nsPtr; + ExtraFrameInfo efi; + + if (objc < 2) { + Tcl_WrongNumArgs(interp, 1, objv, "lambdaExpr ?arg1 arg2 ...?"); + return TCL_ERROR; + } + + /* + * Set lambdaPtr, convert it to lambdaType in the current interp if + * necessary. + */ + + lambdaPtr = objv[1]; + if (lambdaPtr->typePtr == &lambdaType) { + procPtr = lambdaPtr->internalRep.twoPtrValue.ptr1; + } + +#define JOE_EXTENSION 0 +#if JOE_EXTENSION + else { + /* + * Joe English's suggestion to allow cmdNames to function as lambdas. + * Also requires making tclCmdNameType non-static in tclObj.c + */ + + Tcl_Obj *elemPtr; + int numElem; + + if ((lambdaPtr->typePtr == &tclCmdNameType) || + (TclListObjGetElements(interp, lambdaPtr, &numElem, + &elemPtr) == TCL_OK && numElem == 1)) { + return Tcl_EvalObjv(interp, objc-1, objv+1, 0); + } + } +#endif + + if ((procPtr == NULL) || (procPtr->iPtr != iPtr)) { + result = SetLambdaFromAny(interp, lambdaPtr); + if (result != TCL_OK) { + return result; + } + procPtr = lambdaPtr->internalRep.twoPtrValue.ptr1; + } + memset(&cmd, 0, sizeof(Command)); + procPtr->cmdPtr = &cmd; + /* + * TIP#280 (semi-)HACK! + * + * Using cmd.clientData to tell [info frame] how to render the + * 'lambdaPtr'. The InfoFrameCmd will detect this case by testing cmd.hPtr + * for NULL. This condition holds here because of the 'memset' above, and + * nowhere else (in the core). Regular commands always have a valid + * 'hPtr', and lambda's never. + */ + + efi.length = 1; + efi.fields[0].name = "lambda"; + efi.fields[0].proc = NULL; + efi.fields[0].clientData = lambdaPtr; + cmd.clientData = &efi; + + /* + * Find the namespace where this lambda should run, and push a call frame + * for that namespace. Note that TclObjInterpProc() will pop it. + */ + + nsObjPtr = lambdaPtr->internalRep.twoPtrValue.ptr2; + result = TclGetNamespaceFromObj(interp, nsObjPtr, &nsPtr); + if (result != TCL_OK) { + return result; + } + + cmd.nsPtr = (Namespace *) nsPtr; + + isRootEnsemble = (iPtr->ensembleRewrite.sourceObjs == NULL); + if (isRootEnsemble) { + iPtr->ensembleRewrite.sourceObjs = objv; + iPtr->ensembleRewrite.numRemovedObjs = 1; + iPtr->ensembleRewrite.numInsertedObjs = 0; + } else { + iPtr->ensembleRewrite.numInsertedObjs -= 1; + } + + result = PushProcCallFrame((ClientData) procPtr, interp, objc, objv, 1); + if (result == TCL_OK) { + result = TclObjInterpProcCore(interp, objv[1], 2, &MakeLambdaError); + } + + if (isRootEnsemble) { + iPtr->ensembleRewrite.sourceObjs = NULL; + iPtr->ensembleRewrite.numRemovedObjs = 0; + iPtr->ensembleRewrite.numInsertedObjs = 0; + } + + return result; +} + /* *---------------------------------------------------------------------- * - * TclCompileNoOp -- + * MakeLambdaError -- * - * Procedure called to compile noOp's + * Function called by TclObjInterpProc to create the stack information + * upon an error from a lambda term. * * Results: - * The return value is TCL_OK, indicating successful compilation. + * The interpreter's error info trace is set to a value that supplements + * the error code. * * Side effects: - * Instructions are added to envPtr to execute a noOp at runtime. + * none. * *---------------------------------------------------------------------- */ -static int -TclCompileNoOp(interp, parsePtr, envPtr) - Tcl_Interp *interp; /* Used for error reporting. */ - Tcl_Parse *parsePtr; /* Points to a parse structure for the - * command created by Tcl_ParseCommand. */ - CompileEnv *envPtr; /* Holds resulting instructions. */ +static void +MakeLambdaError( + Tcl_Interp *interp, /* The interpreter in which the procedure was + * called. */ + Tcl_Obj *procNameObj) /* Name of the procedure. Used for error + * messages and trace information. */ +{ + int overflow, limit = 60, nameLen; + const char *procName = Tcl_GetStringFromObj(procNameObj, &nameLen); + + overflow = (nameLen > limit); + Tcl_AppendObjToErrorInfo(interp, Tcl_ObjPrintf( + "\n (lambda term \"%.*s%s\" line %d)", + (overflow ? limit : nameLen), procName, + (overflow ? "..." : ""), interp->errorLine)); +} + + +/* + *---------------------------------------------------------------------- + * + * Tcl_DisassembleObjCmd -- + * + * Implementation of the "::tcl::unsupported::disassemble" command. This + * command is not documented, but will disassemble procedures, lambda + * terms and general scripts. Note that will compile terms if necessary + * in order to disassemble them. + * + *---------------------------------------------------------------------- + */ + +int +Tcl_DisassembleObjCmd( + ClientData dummy, /* Not used. */ + Tcl_Interp *interp, /* Current interpreter. */ + int objc, /* Number of arguments. */ + Tcl_Obj *CONST objv[]) /* Argument objects. */ { - Tcl_Token *tokenPtr; - int i, code; - int savedStackDepth = envPtr->currStackDepth; - - tokenPtr = parsePtr->tokenPtr; - for(i = 1; i < parsePtr->numWords; i++) { - tokenPtr = tokenPtr + tokenPtr->numComponents + 1; - envPtr->currStackDepth = savedStackDepth; - - if (tokenPtr->type != TCL_TOKEN_SIMPLE_WORD) { - code = TclCompileTokens(interp, tokenPtr+1, - tokenPtr->numComponents, envPtr); - if (code != TCL_OK) { - return code; + static const char *types[] = { + "lambda", "proc", "script", NULL + }; + enum Types { + DISAS_LAMBDA, DISAS_PROC, DISAS_SCRIPT + }; + int idx, result; + + if (objc != 3) { + Tcl_WrongNumArgs(interp, 1, objv, "type procName|lambdaTerm|script"); + return TCL_ERROR; + } + if (Tcl_GetIndexFromObj(interp, objv[1], types, "type", 0, &idx)!=TCL_OK){ + return TCL_ERROR; + } + + switch ((enum Types) idx) { + case DISAS_LAMBDA: { + Proc *procPtr = NULL; + Command cmd; + Tcl_Obj *nsObjPtr; + Tcl_Namespace *nsPtr; + + /* + * Compile (if uncompiled) and disassemble a lambda term. + */ + + if (objv[2]->typePtr == &lambdaType) { + procPtr = objv[2]->internalRep.twoPtrValue.ptr1; + } + if (procPtr == NULL || procPtr->iPtr != (Interp *) interp) { + result = SetLambdaFromAny(interp, objv[2]); + if (result != TCL_OK) { + return result; } - TclEmitOpcode(INST_POP, envPtr); + procPtr = objv[2]->internalRep.twoPtrValue.ptr1; } + + memset(&cmd, 0, sizeof(Command)); + nsObjPtr = objv[2]->internalRep.twoPtrValue.ptr2; + result = TclGetNamespaceFromObj(interp, nsObjPtr, &nsPtr); + if (result != TCL_OK) { + return result; + } + cmd.nsPtr = (Namespace *) nsPtr; + procPtr->cmdPtr = &cmd; + result = PushProcCallFrame(procPtr, interp, objc, objv, 1); + if (result != TCL_OK) { + return result; + } + TclPopStackFrame(interp); + if (((ByteCode *) procPtr->bodyPtr->internalRep.otherValuePtr)->flags + & TCL_BYTECODE_PRECOMPILED) { + Tcl_AppendResult(interp, "may not disassemble prebuilt bytecode", + NULL); + return TCL_ERROR; + } + Tcl_SetObjResult(interp, TclDisassembleByteCodeObj(procPtr->bodyPtr)); + break; + } + case DISAS_PROC: { + Proc *procPtr = TclFindProc((Interp *) interp, TclGetString(objv[2])); + + if (procPtr == NULL) { + Tcl_AppendResult(interp, "\"", TclGetString(objv[2]), + "\" isn't a procedure", NULL); + return TCL_ERROR; + } + + /* + * Compile (if uncompiled) and disassemble a procedure. + */ + + result = PushProcCallFrame(procPtr, interp, 2, objv+1, 1); + if (result != TCL_OK) { + return result; + } + TclPopStackFrame(interp); + if (((ByteCode *) procPtr->bodyPtr->internalRep.otherValuePtr)->flags + & TCL_BYTECODE_PRECOMPILED) { + Tcl_AppendResult(interp, "may not disassemble prebuilt bytecode", + NULL); + return TCL_ERROR; + } + Tcl_SetObjResult(interp, TclDisassembleByteCodeObj(procPtr->bodyPtr)); + break; + } + case DISAS_SCRIPT: + /* + * Compile and disassemble a script. + */ + + if (objv[2]->typePtr != &tclByteCodeType) { + if (TclSetByteCodeFromAny(interp, objv[2], NULL, NULL) != TCL_OK){ + return TCL_ERROR; + } + } + Tcl_SetObjResult(interp, TclDisassembleByteCodeObj(objv[2])); + break; } - envPtr->currStackDepth = savedStackDepth; - TclEmitPush(TclRegisterLiteral(envPtr, "", 0, /*onHeap*/ 0), envPtr); return TCL_OK; } @@ -2003,4 +2911,3 @@ TclCompileNoOp(interp, parsePtr, envPtr) * fill-column: 78 * End: */ - |