/* * tclProc.c -- * * 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-1996 Sun Microsystems, Inc. * * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. * * SCCS: %Z% $Id: tclProc.c,v 1.10 1998/07/24 13:49:29 surles Exp $ */ #include "tclInt.h" #include "tclCompile.h" /* *---------------------------------------------------------------------- * * Tcl_ProcObjCmd -- * * This object-based procedure is invoked to process the "proc" Tcl * command. See the user documentation for details on what it does. * * Results: * A standard Tcl object result value. * * Side effects: * A new procedure gets created. * *---------------------------------------------------------------------- */ /* 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. */ { register Interp *iPtr = (Interp *) interp; Proc *procPtr; char *fullName, *procName; Namespace *nsPtr, *altNsPtr, *cxtNsPtr; Tcl_Command cmd; Tcl_DString ds; int result; if (objc != 4) { Tcl_WrongNumArgs(interp, 1, objv, "name args body"); return TCL_ERROR; } /* * Determine the namespace where the procedure should reside. Unless * the command name includes namespace qualifiers, this will be the * current namespace. */ fullName = Tcl_GetStringFromObj(objv[1], (int *) NULL); result = TclGetNamespaceForQualName(interp, fullName, (Namespace *) NULL, TCL_LEAVE_ERR_MSG, &nsPtr, &altNsPtr, &cxtNsPtr, &procName); if (result != TCL_OK) { return result; } if (nsPtr == NULL) { Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), "can't create procedure \"", fullName, "\": unknown namespace", (char *) 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; } if ((nsPtr != iPtr->globalNsPtr) && (procName != NULL) && (procName[0] == ':')) { Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), "can't create procedure \"", procName, "\" in non-global namespace with name starting with \":\"", (char *) NULL); return TCL_ERROR; } /* * Create the data structure to represent the procedure. */ if (TclCreateProc(interp, nsPtr, procName, objv[2], objv[3], &procPtr) != TCL_OK) { 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 * qualifiers. To create the new command in the right namespace, we * generate a fully qualified name for it. */ Tcl_DStringInit(&ds); if (nsPtr != iPtr->globalNsPtr) { Tcl_DStringAppend(&ds, nsPtr->fullName, -1); Tcl_DStringAppend(&ds, "::", 2); } 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); /* * 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. */ procPtr->cmdPtr = (Command *) cmd; return TCL_OK; } /* *---------------------------------------------------------------------- * * TclCreateProc -- * * Creates the data associated with a Tcl procedure definition. * * 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. * * Side effects: * If anything goes wrong, this procedure 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 */ 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; char **argArray = NULL; register Proc *procPtr; int i, length, result, numArgs; char *args, *bytes, *p; register CompiledLocal *localPtr; Tcl_Obj *defPtr; /* * 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)) { bytes = Tcl_GetStringFromObj(bodyPtr, &length); bodyPtr = Tcl_NewStringObj(bytes, length); } /* * 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. */ 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; /* * Break up the argument list into argument specifiers, then process * each argument specifier. * THIS FAILS IF THE ARG LIST OBJECT'S STRING REP CONTAINS NULLS. */ args = Tcl_GetStringFromObj(argsPtr, &length); result = Tcl_SplitList(interp, args, &numArgs, &argArray); if (result != TCL_OK) { goto procError; } procPtr->numArgs = numArgs; procPtr->numCompiledLocals = numArgs; for (i = 0; i < numArgs; i++) { int fieldCount, nameLength, valueLength; 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 == '(') { 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); ckfree((char *) fieldValues); goto procError; } } p++; } /* * 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->isArg = 1; localPtr->isTemp = 0; localPtr->flags = VAR_SCALAR; localPtr->resolveInfo = NULL; if (fieldCount == 2) { localPtr->defValuePtr = Tcl_NewStringObj(fieldValues[1], valueLength); Tcl_IncrRefCount(localPtr->defValuePtr); } else { localPtr->defValuePtr = NULL; } strcpy(localPtr->name, fieldValues[0]); 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: Tcl_DecrRefCount(bodyPtr); while (procPtr->firstLocalPtr != NULL) { localPtr = procPtr->firstLocalPtr; procPtr->firstLocalPtr = localPtr->nextPtr; defPtr = localPtr->defValuePtr; if (defPtr != NULL) { Tcl_DecrRefCount(defPtr); } ckfree((char *) localPtr); } ckfree((char *) procPtr); if (argArray != NULL) { ckfree((char *) argArray); } return TCL_ERROR; } /* *---------------------------------------------------------------------- * * 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. * * Results: * The return value is -1 if an error occurred in finding the * frame (in this case an error message is left in interp->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. * *---------------------------------------------------------------------- */ int TclGetFrame(interp, string, framePtrPtr) Tcl_Interp *interp; /* Interpreter in which to find frame. */ char *string; /* 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; CallFrame *framePtr; /* * Parse string to figure out which level number to go to. */ result = 1; curLevel = (iPtr->varFramePtr == NULL) ? 0 : iPtr->varFramePtr->level; if (*string == '#') { if (Tcl_GetInt(interp, string+1, &level) != TCL_OK) { return -1; } if (level < 0) { levelError: Tcl_AppendResult(interp, "bad level \"", string, "\"", (char *) NULL); return -1; } } else if (isdigit(UCHAR(*string))) { if (Tcl_GetInt(interp, string, &level) != TCL_OK) { return -1; } level = curLevel - level; } else { level = curLevel - 1; result = 0; } /* * Figure out which frame to use, and modify the interpreter so * its variables come from that frame. */ 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; } } *framePtrPtr = framePtr; return result; } /* *---------------------------------------------------------------------- * * Tcl_UplevelObjCmd -- * * This object procedure 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. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ /* 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. */ { register Interp *iPtr = (Interp *) interp; char *optLevel; int length, result; CallFrame *savedVarFramePtr, *framePtr; if (objc < 2) { uplevelSyntax: Tcl_WrongNumArgs(interp, 1, objv, "?level? command ?arg ...?"); return TCL_ERROR; } /* * Find the level to use for executing the command. * THIS FAILS IF THE OBJECT RESULT'S STRING REP CONTAINS A NULL. */ optLevel = Tcl_GetStringFromObj(objv[1], &length); result = TclGetFrame(interp, optLevel, &framePtr); if (result == -1) { return TCL_ERROR; } objc -= (result+1); if (objc == 0) { goto uplevelSyntax; } objv += (result+1); /* * Modify the interpreter state to execute in the given frame. */ savedVarFramePtr = iPtr->varFramePtr; iPtr->varFramePtr = framePtr; /* * Execute the residual arguments as a command. */ if (objc == 1) { result = Tcl_EvalObj(interp, objv[0]); } else { Tcl_Obj *cmdObjPtr = Tcl_ConcatObj(objc, objv); result = Tcl_EvalObj(interp, cmdObjPtr); Tcl_DecrRefCount(cmdObjPtr); /* done with object */ } if (result == TCL_ERROR) { char msg[60]; sprintf(msg, "\n (\"uplevel\" body line %d)", interp->errorLine); Tcl_AddObjErrorInfo(interp, msg, -1); } /* * Restore the variable frame, and return. */ iPtr->varFramePtr = savedVarFramePtr; return result; } /* *---------------------------------------------------------------------- * * TclFindProc -- * * Given the name of a procedure, return a pointer to the * record describing the procedure. * * 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. * * Side effects: * None. * *---------------------------------------------------------------------- */ Proc * TclFindProc(iPtr, procName) Interp *iPtr; /* Interpreter in which to look. */ 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); if (cmd == (Tcl_Command) NULL) { return NULL; } cmdPtr = (Command *) cmd; origCmd = TclGetOriginalCommand(cmd); if (origCmd != NULL) { cmdPtr = (Command *) origCmd; } if (cmdPtr->proc != TclProcInterpProc) { return NULL; } return (Proc *) cmdPtr->clientData; } /* *---------------------------------------------------------------------- * * TclIsProc -- * * 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. * * Side effects: * None. * *---------------------------------------------------------------------- */ Proc * TclIsProc(cmdPtr) Command *cmdPtr; /* Command to test. */ { Tcl_Command origCmd; origCmd = TclGetOriginalCommand((Tcl_Command) cmdPtr); if (origCmd != NULL) { cmdPtr = (Command *) origCmd; } if (cmdPtr->proc == TclProcInterpProc) { return (Proc *) cmdPtr->clientData; } return (Proc *) 0; } /* *---------------------------------------------------------------------- * * TclProcInterpProc -- * * When a Tcl procedure gets invoked with an argc/argv array of * strings, this routine gets invoked to interpret the procedure. * * Results: * A standard Tcl result value, usually TCL_OK. * * Side effects: * Depends on the commands in the procedure. * *---------------------------------------------------------------------- */ 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 char **argv; /* Argument values. */ { register Tcl_Obj *objPtr; register int i; int result; /* * 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. */ #define NUM_ARGS 20 Tcl_Obj *(objStorage[NUM_ARGS]); register Tcl_Obj **objv = objStorage; /* * 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 ((argc + 1) > NUM_ARGS) { objv = (Tcl_Obj **) ckalloc((unsigned)(argc + 1) * sizeof(Tcl_Obj *)); } for (i = 0; i < argc; i++) { objv[i] = Tcl_NewStringObj(argv[i], -1); Tcl_IncrRefCount(objv[i]); } 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. * FAILS IF OBJECT RESULT'S STRING REPRESENTATION CONTAINS NULLS. */ Tcl_SetResult(interp, TclGetStringFromObj(Tcl_GetObjResult(interp), (int *) NULL), TCL_VOLATILE); /* * Decrement the ref counts on the objv elements since we are done * with them. */ for (i = 0; i < argc; i++) { objPtr = objv[i]; TclDecrRefCount(objPtr); } /* * Free the objv array if malloc'ed storage was used. */ if (objv != objStorage) { ckfree((char *) objv); } return result; #undef NUM_ARGS } /* *---------------------------------------------------------------------- * * 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, interp, objc, objv) ClientData clientData; /* Record describing procedure to be * interpreted. */ 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. */ { Interp *iPtr = (Interp *) interp; Proc *procPtr = (Proc *) clientData; Namespace *nsPtr = procPtr->cmdPtr->nsPtr; CallFrame frame; register CallFrame *framePtr = &frame; register Var *varPtr, *resolvedVarPtr; register CompiledLocal *localPtr; Tcl_ResolvedVarInfo *resVarInfo; char *procName, *bytes; int nameLen, localCt, numArgs, argCt, length, i, result; /* * 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. */ #define NUM_LOCALS 20 Var localStorage[NUM_LOCALS]; Var *compiledLocals = localStorage; /* * Get the procedure's name. * THIS FAILS IF THE PROC NAME'S STRING REP HAS A NULL. */ procName = Tcl_GetStringFromObj(objv[0], &nameLen); /* * 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. */ result = TclProcCompileProc(interp, procPtr, procPtr->bodyPtr, nsPtr, "body of proc", procName); if (result != TCL_OK) { return result; } /* * Create the "compiledLocals" array. Make sure it is large enough to * hold all the procedure's compiled local variables, including its * formal parameters. */ localCt = procPtr->numCompiledLocals; if (localCt > NUM_LOCALS) { compiledLocals = (Var *) ckalloc((unsigned) localCt * sizeof(Var)); } /* * 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. */ result = Tcl_PushCallFrame(interp, (Tcl_CallFrame *) framePtr, (Tcl_Namespace *) nsPtr, /*isProcCallFrame*/ 1); if (result != TCL_OK) { return result; } framePtr->objc = objc; framePtr->objv = objv; /* ref counts for args are incremented below */ framePtr->procPtr = procPtr; framePtr->numCompiledLocals = localCt; framePtr->compiledLocals = compiledLocals; /* * 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. */ varPtr = framePtr->compiledLocals; for (localPtr = procPtr->firstLocalPtr; localPtr != NULL; localPtr = localPtr->nextPtr) { resVarInfo = localPtr->resolveInfo; resolvedVarPtr = NULL; if (resVarInfo && resVarInfo->fetchProc) { resolvedVarPtr = (Var*) (*resVarInfo->fetchProc)(interp, resVarInfo->identity); } if (resolvedVarPtr) { varPtr->name = localPtr->name; /* will be just '\0' if temp var */ varPtr->nsPtr = NULL; varPtr->hPtr = NULL; varPtr->refCount = 0; varPtr->tracePtr = NULL; varPtr->searchPtr = NULL; varPtr->flags = 0; TclSetVarLink(varPtr); varPtr->value.linkPtr = resolvedVarPtr; resolvedVarPtr->refCount++; } else { varPtr->value.objPtr = NULL; varPtr->name = localPtr->name; /* will be just '\0' if temp var */ varPtr->nsPtr = NULL; varPtr->hPtr = NULL; varPtr->refCount = 0; varPtr->tracePtr = NULL; varPtr->searchPtr = NULL; varPtr->flags = (localPtr->flags | VAR_UNDEFINED); } varPtr++; } /* * 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 (!localPtr->isArg) { panic("TclObjInterpProc: local variable %s is not argument but should be", localPtr->name); return TCL_ERROR; } if (localPtr->isTemp) { panic("TclObjInterpProc: local variable %d is temporary but should be an argument", i); return TCL_ERROR; } /* * Handle the special case of the last formal being "args". When * it occurs, assign it a list consisting of all the remaining * actual arguments. */ 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 */ varPtr->flags &= ~VAR_UNDEFINED; argCt = 0; break; /* done processing args */ } else if (argCt > 0) { Tcl_Obj *objPtr = objv[i]; varPtr->value.objPtr = objPtr; varPtr->flags &= ~VAR_UNDEFINED; Tcl_IncrRefCount(objPtr); /* since the local variable now has * another reference to object. */ } else if (localPtr->defValuePtr != NULL) { Tcl_Obj *objPtr = localPtr->defValuePtr; varPtr->value.objPtr = objPtr; varPtr->flags &= ~VAR_UNDEFINED; Tcl_IncrRefCount(objPtr); /* since the local variable now has * another reference to object. */ } else { Tcl_ResetResult(interp); Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), "no value given for parameter \"", localPtr->name, "\" to \"", Tcl_GetStringFromObj(objv[0], (int *) NULL), "\"", (char *) NULL); result = TCL_ERROR; goto procDone; } varPtr++; localPtr = localPtr->nextPtr; } if (argCt > 0) { Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), "called \"", Tcl_GetStringFromObj(objv[0], (int *) NULL), "\" with too many arguments", (char *) NULL); result = TCL_ERROR; goto procDone; } /* * Invoke the commands in the procedure's body. */ if (tclTraceExec >= 1) { fprintf(stdout, "Calling proc "); for (i = 0; i < objc; i++) { bytes = Tcl_GetStringFromObj(objv[i], &length); TclPrintSource(stdout, bytes, TclMin(length, 15)); fprintf(stdout, " "); } fprintf(stdout, "\n"); fflush(stdout); } iPtr->returnCode = TCL_OK; procPtr->refCount++; result = Tcl_EvalObj(interp, procPtr->bodyPtr); procPtr->refCount--; if (procPtr->refCount <= 0) { TclProcCleanupProc(procPtr); } if (result != TCL_OK) { if (result == TCL_RETURN) { result = TclUpdateReturnInfo(iPtr); } else if (result == TCL_ERROR) { char msg[100]; sprintf(msg, "\n (procedure \"%.50s\" line %d)", procName, iPtr->errorLine); Tcl_AddObjErrorInfo(interp, msg, -1); } else if (result == TCL_BREAK) { Tcl_ResetResult(interp); Tcl_AppendToObj(Tcl_GetObjResult(interp), "invoked \"break\" outside of a loop", -1); result = TCL_ERROR; } else if (result == TCL_CONTINUE) { Tcl_ResetResult(interp); Tcl_AppendToObj(Tcl_GetObjResult(interp), "invoked \"continue\" outside of a loop", -1); result = TCL_ERROR; } } procDone: /* * Pop and free the call frame for this procedure invocation. */ Tcl_PopCallFrame(interp); /* * Free the compiledLocals array if malloc'ed storage was used. */ if (compiledLocals != localStorage) { ckfree((char *) compiledLocals); } return result; #undef NUM_LOCALS } /* *---------------------------------------------------------------------- * * 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. * * Results: * None. * * Side effects: * 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. */ { Interp *iPtr = (Interp*)interp; int result; Tcl_CallFrame frame; Proc *saveProcPtr; /* * 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 (bodyPtr->typePtr == &tclByteCodeType) { ByteCode *codePtr = (ByteCode *) bodyPtr->internalRep.otherValuePtr; if ((codePtr->iPtr != iPtr) || (codePtr->compileEpoch != iPtr->compileEpoch) || (codePtr->nsPtr != nsPtr) || (codePtr->nsEpoch != nsPtr->resolverEpoch)) { tclByteCodeType.freeIntRepProc(bodyPtr); bodyPtr->typePtr = (Tcl_ObjType *) NULL; } } if (bodyPtr->typePtr != &tclByteCodeType) { char buf[100]; int numChars; char *ellipsis; if (tclTraceCompile >= 1) { /* * 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); } /* * 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. */ saveProcPtr = iPtr->compiledProcPtr; iPtr->compiledProcPtr = procPtr; result = Tcl_PushCallFrame(interp, &frame, (Tcl_Namespace*)nsPtr, /* isProcCallFrame */ 0); if (result == TCL_OK) { result = tclByteCodeType.setFromAnyProc(interp, bodyPtr); Tcl_PopCallFrame(interp); } iPtr->compiledProcPtr = saveProcPtr; if (result != TCL_OK) { if (result == TCL_ERROR) { numChars = strlen(procName); ellipsis = ""; if (numChars > 50) { numChars = 50; ellipsis = "..."; } sprintf(buf, "\n (compiling %s \"%.*s%s\", line %d)", description, numChars, procName, ellipsis, interp->errorLine); Tcl_AddObjErrorInfo(interp, buf, -1); } return result; } } return TCL_OK; } /* *---------------------------------------------------------------------- * * 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. * * 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. * *---------------------------------------------------------------------- */ void TclProcDeleteProc(clientData) ClientData clientData; /* Procedure to be deleted. */ { Proc *procPtr = (Proc *) clientData; procPtr->refCount--; if (procPtr->refCount <= 0) { TclProcCleanupProc(procPtr); } } /* *---------------------------------------------------------------------- * * 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. * * Results: * None. * * Side effects: * Memory gets freed. * *---------------------------------------------------------------------- */ void TclProcCleanupProc(procPtr) register Proc *procPtr; /* Procedure to be deleted. */ { register CompiledLocal *localPtr; Tcl_Obj *bodyPtr = procPtr->bodyPtr; Tcl_Obj *defPtr; Tcl_ResolvedVarInfo *resVarInfo; if (bodyPtr != NULL) { Tcl_DecrRefCount(bodyPtr); } for (localPtr = procPtr->firstLocalPtr; localPtr != NULL; ) { CompiledLocal *nextPtr = localPtr->nextPtr; resVarInfo = localPtr->resolveInfo; if (resVarInfo && resVarInfo->deleteProc) { (*resVarInfo->deleteProc)(resVarInfo->identity); resVarInfo->identity = NULL; ckfree((char *) resVarInfo); } if (localPtr->defValuePtr != NULL) { defPtr = localPtr->defValuePtr; Tcl_DecrRefCount(defPtr); } ckfree((char *) localPtr); localPtr = nextPtr; } ckfree((char *) 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. * * Results: * The return value is the true completion code to use for * the procedure, instead of TCL_RETURN. * * Side effects: * The errorInfo and errorCode variables may get modified. * *---------------------------------------------------------------------- */ int TclUpdateReturnInfo(iPtr) Interp *iPtr; /* Interpreter for which TCL_RETURN * exception is being processed. */ { int code; code = iPtr->returnCode; iPtr->returnCode = TCL_OK; if (code == TCL_ERROR) { Tcl_SetVar2((Tcl_Interp *) iPtr, "errorCode", (char *) NULL, (iPtr->errorCode != NULL) ? iPtr->errorCode : "NONE", TCL_GLOBAL_ONLY); iPtr->flags |= ERROR_CODE_SET; if (iPtr->errorInfo != NULL) { Tcl_SetVar2((Tcl_Interp *) iPtr, "errorInfo", (char *) NULL, iPtr->errorInfo, TCL_GLOBAL_ONLY); iPtr->flags |= ERR_IN_PROGRESS; } } return code; }