/* * ------------------------------------------------------------------------ * PACKAGE: [incr Tcl] * DESCRIPTION: Object-Oriented Extensions to Tcl * * [incr Tcl] provides object-oriented extensions to Tcl, much as * C++ provides object-oriented extensions to C. It provides a means * of encapsulating related procedures together with their shared data * in a local namespace that is hidden from the outside world. It * promotes code re-use through inheritance. More than anything else, * it encourages better organization of Tcl applications through the * object-oriented paradigm, leading to code that is easier to * understand and maintain. * * These procedures handle commands available within a class scope. * In [incr Tcl], the term "method" is used for a procedure that has * access to object-specific data, while the term "proc" is used for * a procedure that has access only to common class data. * * ======================================================================== * AUTHOR: Michael J. McLennan * Bell Labs Innovations for Lucent Technologies * mmclennan@lucent.com * http://www.tcltk.com/itcl * ======================================================================== * Copyright (c) 1993-1998 Lucent Technologies, Inc. * ------------------------------------------------------------------------ * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "itclInt.h" /* * FORWARD DECLARATIONS */ static int ItclParseConfig _ANSI_ARGS_((Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[], ItclObject *contextObj, int *rargc, ItclVarDefn ***rvars, char ***rvals)); static int ItclHandleConfig _ANSI_ARGS_((Tcl_Interp *interp, int argc, ItclVarDefn **vars, char **vals, ItclObject *contextObj)); /* * ------------------------------------------------------------------------ * Itcl_BodyCmd() * * Invoked by Tcl whenever the user issues an "itcl::body" command to * define or redefine the implementation for a class method/proc. * Handles the following syntax: * * itcl::body :: * * Looks for an existing class member function with the name , * and if found, tries to assign the implementation. If an argument * list was specified in the original declaration, it must match * or an error is flagged. If has the form "@name" * then it is treated as a reference to a C handling procedure; * otherwise, it is taken as a body of Tcl statements. * * Returns TCL_OK/TCL_ERROR to indicate success/failure. * ------------------------------------------------------------------------ */ /* ARGSUSED */ int Itcl_BodyCmd(dummy, interp, objc, objv) ClientData dummy; /* unused */ Tcl_Interp *interp; /* current interpreter */ int objc; /* number of arguments */ Tcl_Obj *CONST objv[]; /* argument objects */ { int status = TCL_OK; char *head, *tail, *token, *arglist, *body; ItclClass *cdefn; ItclMemberFunc *mfunc; Tcl_HashEntry *entry; Tcl_DString buffer; if (objc != 4) { token = Tcl_GetStringFromObj(objv[0], (int*)NULL); Tcl_AppendResult(interp, "wrong # args: should be \"", token, " class::func arglist body\"", (char*)NULL); return TCL_ERROR; } /* * Parse the member name "namesp::namesp::class::func". * Make sure that a class name was specified, and that the * class exists. */ token = Tcl_GetStringFromObj(objv[1], (int*)NULL); Itcl_ParseNamespPath(token, &buffer, &head, &tail); if (!head || *head == '\0') { Tcl_AppendResult(interp, "missing class specifier for body declaration \"", token, "\"", (char*)NULL); status = TCL_ERROR; goto bodyCmdDone; } cdefn = Itcl_FindClass(interp, head, /* autoload */ 1); if (cdefn == NULL) { status = TCL_ERROR; goto bodyCmdDone; } /* * Find the function and try to change its implementation. * Note that command resolution table contains *all* functions, * even those in a base class. Make sure that the class * containing the method definition is the requested class. */ mfunc = NULL; entry = Tcl_FindHashEntry(&cdefn->resolveCmds, tail); if (entry) { mfunc = (ItclMemberFunc*)Tcl_GetHashValue(entry); if (mfunc->member->classDefn != cdefn) { mfunc = NULL; } } if (mfunc == NULL) { Tcl_AppendResult(interp, "function \"", tail, "\" is not defined in class \"", cdefn->fullname, "\"", (char*)NULL); status = TCL_ERROR; goto bodyCmdDone; } arglist = Tcl_GetStringFromObj(objv[2], (int*)NULL); body = Tcl_GetStringFromObj(objv[3], (int*)NULL); if (Itcl_ChangeMemberFunc(interp, mfunc, arglist, body) != TCL_OK) { status = TCL_ERROR; goto bodyCmdDone; } bodyCmdDone: Tcl_DStringFree(&buffer); return status; } /* * ------------------------------------------------------------------------ * Itcl_ConfigBodyCmd() * * Invoked by Tcl whenever the user issues an "itcl::configbody" command * to define or redefine the configuration code associated with a * public variable. Handles the following syntax: * * itcl::configbody :: * * Looks for an existing public variable with the name , * and if found, tries to assign the implementation. If has * the form "@name" then it is treated as a reference to a C handling * procedure; otherwise, it is taken as a body of Tcl statements. * * Returns TCL_OK/TCL_ERROR to indicate success/failure. * ------------------------------------------------------------------------ */ /* ARGSUSED */ int Itcl_ConfigBodyCmd(dummy, interp, objc, objv) ClientData dummy; /* unused */ Tcl_Interp *interp; /* current interpreter */ int objc; /* number of arguments */ Tcl_Obj *CONST objv[]; /* argument objects */ { int status = TCL_OK; char *head, *tail, *token; Tcl_DString buffer; ItclClass *cdefn; ItclVarLookup *vlookup; ItclMember *member; ItclMemberCode *mcode; Tcl_HashEntry *entry; if (objc != 3) { Tcl_WrongNumArgs(interp, 1, objv, "class::option body"); return TCL_ERROR; } /* * Parse the member name "namesp::namesp::class::option". * Make sure that a class name was specified, and that the * class exists. */ token = Tcl_GetStringFromObj(objv[1], (int*)NULL); Itcl_ParseNamespPath(token, &buffer, &head, &tail); if (!head || *head == '\0') { Tcl_AppendResult(interp, "missing class specifier for body declaration \"", token, "\"", (char*)NULL); status = TCL_ERROR; goto configBodyCmdDone; } cdefn = Itcl_FindClass(interp, head, /* autoload */ 1); if (cdefn == NULL) { status = TCL_ERROR; goto configBodyCmdDone; } /* * Find the variable and change its implementation. * Note that variable resolution table has *all* variables, * even those in a base class. Make sure that the class * containing the variable definition is the requested class. */ vlookup = NULL; entry = Tcl_FindHashEntry(&cdefn->resolveVars, tail); if (entry) { vlookup = (ItclVarLookup*)Tcl_GetHashValue(entry); if (vlookup->vdefn->member->classDefn != cdefn) { vlookup = NULL; } } if (vlookup == NULL) { Tcl_AppendResult(interp, "option \"", tail, "\" is not defined in class \"", cdefn->fullname, "\"", (char*)NULL); status = TCL_ERROR; goto configBodyCmdDone; } member = vlookup->vdefn->member; if (member->protection != ITCL_PUBLIC) { Tcl_AppendResult(interp, "option \"", member->fullname, "\" is not a public configuration option", (char*)NULL); status = TCL_ERROR; goto configBodyCmdDone; } token = Tcl_GetStringFromObj(objv[2], (int*)NULL); if (Itcl_CreateMemberCode(interp, cdefn, (char*)NULL, token, &mcode) != TCL_OK) { status = TCL_ERROR; goto configBodyCmdDone; } Itcl_PreserveData((ClientData)mcode); Itcl_EventuallyFree((ClientData)mcode, (Tcl_FreeProc*) Itcl_DeleteMemberCode); if (member->code) { Itcl_ReleaseData((ClientData)member->code); } member->code = mcode; configBodyCmdDone: Tcl_DStringFree(&buffer); return status; } /* * ------------------------------------------------------------------------ * Itcl_CreateMethod() * * Installs a method into the namespace associated with a class. * If another command with the same name is already installed, then * it is overwritten. * * Returns TCL_OK on success, or TCL_ERROR (along with an error message * in the specified interp) if anything goes wrong. * ------------------------------------------------------------------------ */ int Itcl_CreateMethod(interp, cdefn, name, arglist, body) Tcl_Interp* interp; /* interpreter managing this action */ ItclClass *cdefn; /* class definition */ CONST char* name; /* name of new method */ CONST char* arglist; /* space-separated list of arg names */ CONST char* body; /* body of commands for the method */ { ItclMemberFunc *mfunc; Tcl_DString buffer; /* * Make sure that the method name does not contain anything * goofy like a "::" scope qualifier. */ if (strstr(name,"::")) { Tcl_AppendResult(interp, "bad method name \"", name, "\"", (char*)NULL); return TCL_ERROR; } /* * Create the method definition. */ if (Itcl_CreateMemberFunc(interp, cdefn, name, arglist, body, &mfunc) != TCL_OK) { return TCL_ERROR; } /* * Build a fully-qualified name for the method, and install * the command handler. */ Tcl_DStringInit(&buffer); Tcl_DStringAppend(&buffer, cdefn->namesp->fullName, -1); Tcl_DStringAppend(&buffer, "::", 2); Tcl_DStringAppend(&buffer, name, -1); name = Tcl_DStringValue(&buffer); Itcl_PreserveData((ClientData)mfunc); mfunc->accessCmd = Tcl_CreateObjCommand(interp, name, Itcl_ExecMethod, (ClientData)mfunc, Itcl_ReleaseData); Tcl_DStringFree(&buffer); return TCL_OK; } /* * ------------------------------------------------------------------------ * Itcl_CreateProc() * * Installs a class proc into the namespace associated with a class. * If another command with the same name is already installed, then * it is overwritten. Returns TCL_OK on success, or TCL_ERROR (along * with an error message in the specified interp) if anything goes * wrong. * ------------------------------------------------------------------------ */ int Itcl_CreateProc(interp, cdefn, name, arglist, body) Tcl_Interp* interp; /* interpreter managing this action */ ItclClass *cdefn; /* class definition */ CONST char* name; /* name of new proc */ CONST char* arglist; /* space-separated list of arg names */ CONST char* body; /* body of commands for the proc */ { ItclMemberFunc *mfunc; Tcl_DString buffer; /* * Make sure that the proc name does not contain anything * goofy like a "::" scope qualifier. */ if (strstr(name,"::")) { Tcl_AppendResult(interp, "bad proc name \"", name, "\"", (char*)NULL); return TCL_ERROR; } /* * Create the proc definition. */ if (Itcl_CreateMemberFunc(interp, cdefn, name, arglist, body, &mfunc) != TCL_OK) { return TCL_ERROR; } /* * Mark procs as "common". This distinguishes them from methods. */ mfunc->member->flags |= ITCL_COMMON; /* * Build a fully-qualified name for the proc, and install * the command handler. */ Tcl_DStringInit(&buffer); Tcl_DStringAppend(&buffer, cdefn->namesp->fullName, -1); Tcl_DStringAppend(&buffer, "::", 2); Tcl_DStringAppend(&buffer, name, -1); name = Tcl_DStringValue(&buffer); Itcl_PreserveData((ClientData)mfunc); mfunc->accessCmd = Tcl_CreateObjCommand(interp, name, Itcl_ExecProc, (ClientData)mfunc, Itcl_ReleaseData); Tcl_DStringFree(&buffer); return TCL_OK; } /* * ------------------------------------------------------------------------ * Itcl_CreateMemberFunc() * * Creates the data record representing a member function. This * includes the argument list and the body of the function. If the * body is of the form "@name", then it is treated as a label for * a C procedure registered by Itcl_RegisterC(). * * If any errors are encountered, this procedure returns TCL_ERROR * along with an error message in the interpreter. Otherwise, it * returns TCL_OK, and "mfuncPtr" returns a pointer to the new * member function. * ------------------------------------------------------------------------ */ int Itcl_CreateMemberFunc(interp, cdefn, name, arglist, body, mfuncPtr) Tcl_Interp* interp; /* interpreter managing this action */ ItclClass *cdefn; /* class definition */ CONST char* name; /* name of new member */ CONST char* arglist; /* space-separated list of arg names */ CONST char* body; /* body of commands for the method */ ItclMemberFunc** mfuncPtr; /* returns: pointer to new method defn */ { int newEntry; ItclMemberFunc *mfunc; ItclMemberCode *mcode; Tcl_HashEntry *entry; /* * Add the member function to the list of functions for * the class. Make sure that a member function with the * same name doesn't already exist. */ entry = Tcl_CreateHashEntry(&cdefn->functions, name, &newEntry); if (!newEntry) { Tcl_AppendResult(interp, "\"", name, "\" already defined in class \"", cdefn->fullname, "\"", (char*)NULL); return TCL_ERROR; } /* * Try to create the implementation for this command member. */ if (Itcl_CreateMemberCode(interp, cdefn, arglist, body, &mcode) != TCL_OK) { Tcl_DeleteHashEntry(entry); return TCL_ERROR; } Itcl_PreserveData((ClientData)mcode); Itcl_EventuallyFree((ClientData)mcode, (Tcl_FreeProc*) Itcl_DeleteMemberCode); /* * Allocate a member function definition and return. */ mfunc = (ItclMemberFunc*)ckalloc(sizeof(ItclMemberFunc)); mfunc->member = Itcl_CreateMember(interp, cdefn, name); mfunc->member->code = mcode; if (mfunc->member->protection == ITCL_DEFAULT_PROTECT) { mfunc->member->protection = ITCL_PUBLIC; } mfunc->arglist = NULL; mfunc->argcount = 0; mfunc->accessCmd = NULL; if (arglist) { mfunc->member->flags |= ITCL_ARG_SPEC; } if (mcode->arglist) { Itcl_CreateArgList(interp, arglist, &mfunc->argcount, &mfunc->arglist); } if (strcmp(name,"constructor") == 0) { mfunc->member->flags |= ITCL_CONSTRUCTOR; } if (strcmp(name,"destructor") == 0) { mfunc->member->flags |= ITCL_DESTRUCTOR; } Tcl_SetHashValue(entry, (ClientData)mfunc); Itcl_PreserveData((ClientData)mfunc); Itcl_EventuallyFree((ClientData)mfunc, (Tcl_FreeProc*) Itcl_DeleteMemberFunc); *mfuncPtr = mfunc; return TCL_OK; } /* * ------------------------------------------------------------------------ * Itcl_ChangeMemberFunc() * * Modifies the data record representing a member function. This * is usually the body of the function, but can include the argument * list if it was not defined when the member was first created. * If the body is of the form "@name", then it is treated as a label * for a C procedure registered by Itcl_RegisterC(). * * If any errors are encountered, this procedure returns TCL_ERROR * along with an error message in the interpreter. Otherwise, it * returns TCL_OK, and "mfuncPtr" returns a pointer to the new * member function. * ------------------------------------------------------------------------ */ int Itcl_ChangeMemberFunc(interp, mfunc, arglist, body) Tcl_Interp* interp; /* interpreter managing this action */ ItclMemberFunc* mfunc; /* command member being changed */ CONST char* arglist; /* space-separated list of arg names */ CONST char* body; /* body of commands for the method */ { ItclMemberCode *mcode = NULL; Tcl_Obj *objPtr; /* * Try to create the implementation for this command member. */ if (Itcl_CreateMemberCode(interp, mfunc->member->classDefn, arglist, body, &mcode) != TCL_OK) { return TCL_ERROR; } /* * If the argument list was defined when the function was * created, compare the arg lists or usage strings to make sure * that the interface is not being redefined. */ if ((mfunc->member->flags & ITCL_ARG_SPEC) != 0 && !Itcl_EquivArgLists(mfunc->arglist, mfunc->argcount, mcode->arglist, mcode->argcount)) { objPtr = Itcl_ArgList(mfunc->argcount, mfunc->arglist); Tcl_IncrRefCount(objPtr); Tcl_AppendResult(interp, "argument list changed for function \"", mfunc->member->fullname, "\": should be \"", Tcl_GetStringFromObj(objPtr, (int*)NULL), "\"", (char*)NULL); Tcl_DecrRefCount(objPtr); Itcl_DeleteMemberCode((char*)mcode); return TCL_ERROR; } /* * Free up the old implementation and install the new one. */ Itcl_PreserveData((ClientData)mcode); Itcl_EventuallyFree((ClientData)mcode, (Tcl_FreeProc*) Itcl_DeleteMemberCode); Itcl_ReleaseData((ClientData)mfunc->member->code); mfunc->member->code = mcode; return TCL_OK; } /* * ------------------------------------------------------------------------ * Itcl_DeleteMemberFunc() * * Destroys all data associated with the given member function definition. * Usually invoked by the interpreter when a member function is deleted. * ------------------------------------------------------------------------ */ void Itcl_DeleteMemberFunc(cdata) CONST char* cdata; /* pointer to member function definition */ { ItclMemberFunc* mfunc = (ItclMemberFunc*)cdata; if (mfunc) { Itcl_DeleteMember(mfunc->member); if (mfunc->arglist) { Itcl_DeleteArgList(mfunc->arglist); } ckfree((char*)mfunc); } } /* * ------------------------------------------------------------------------ * Itcl_CreateMemberCode() * * Creates the data record representing the implementation behind a * class member function. This includes the argument list and the body * of the function. If the body is of the form "@name", then it is * treated as a label for a C procedure registered by Itcl_RegisterC(). * * The implementation is kept by the member function definition, and * controlled by a preserve/release paradigm. That way, if it is in * use while it is being redefined, it will stay around long enough * to avoid a core dump. * * If any errors are encountered, this procedure returns TCL_ERROR * along with an error message in the interpreter. Otherwise, it * returns TCL_OK, and "mcodePtr" returns a pointer to the new * implementation. * ------------------------------------------------------------------------ */ int Itcl_CreateMemberCode(interp, cdefn, arglist, body, mcodePtr) Tcl_Interp* interp; /* interpreter managing this action */ ItclClass *cdefn; /* class containing this member */ CONST char* arglist; /* space-separated list of arg names */ CONST char* body; /* body of commands for the method */ ItclMemberCode** mcodePtr; /* returns: pointer to new implementation */ { int argc; CompiledLocal *args, *localPtr; ItclMemberCode *mcode; Proc *procPtr; /* * Allocate some space to hold the implementation. */ mcode = (ItclMemberCode*)ckalloc(sizeof(ItclMemberCode)); memset(mcode, 0, sizeof(ItclMemberCode)); if (arglist) { if (Itcl_CreateArgList(interp, arglist, &argc, &args) != TCL_OK) { Itcl_DeleteMemberCode((char*)mcode); return TCL_ERROR; } mcode->argcount = argc; mcode->arglist = args; mcode->flags |= ITCL_ARG_SPEC; } else { argc = 0; args = NULL; } /* * Create a standard Tcl Proc representation for this code body. * This is required, since the Tcl compiler looks for a proc * when handling things such as the call frame context and * compiled locals. */ procPtr = (Proc*)ckalloc(sizeof(Proc)); mcode->procPtr = procPtr; procPtr->iPtr = (Interp*)interp; procPtr->refCount = 1; procPtr->cmdPtr = (Command*)ckalloc(sizeof(Command)); memset(procPtr->cmdPtr, 0, sizeof(Command)); procPtr->cmdPtr->nsPtr = (Namespace*)cdefn->namesp; if (body) { procPtr->bodyPtr = Tcl_NewStringObj(body, -1); } else { procPtr->bodyPtr = Tcl_NewStringObj("", -1); mcode->flags |= ITCL_IMPLEMENT_NONE; } Tcl_IncrRefCount(procPtr->bodyPtr); /* * Plug the argument list into the "compiled locals" list. * * NOTE: The storage for this argument list is owned by * the caller, so although we plug it in here, it is not * our responsibility to free it. */ procPtr->firstLocalPtr = args; procPtr->lastLocalPtr = NULL; for (localPtr=mcode->arglist; localPtr; localPtr=localPtr->nextPtr) { procPtr->lastLocalPtr = localPtr; } procPtr->numArgs = argc; procPtr->numCompiledLocals = argc; /* * If the body definition starts with '@', then treat the value * as a symbolic name for a C procedure. */ if (body == NULL) { /* No-op */ } else if (*body == '@') { Tcl_CmdProc *argCmdProc; Tcl_ObjCmdProc *objCmdProc; ClientData cdata; if (!Itcl_FindC(interp, body+1, &argCmdProc, &objCmdProc, &cdata)) { Tcl_AppendResult(interp, "no registered C procedure with name \"", body+1, "\"", (char*)NULL); Itcl_DeleteMemberCode((char*)mcode); return TCL_ERROR; } if (objCmdProc != NULL) { mcode->flags |= ITCL_IMPLEMENT_OBJCMD; mcode->cfunc.objCmd = objCmdProc; mcode->clientData = cdata; } else if (argCmdProc != NULL) { mcode->flags |= ITCL_IMPLEMENT_ARGCMD; mcode->cfunc.argCmd = argCmdProc; mcode->clientData = cdata; } } /* * Otherwise, treat the body as a chunk of Tcl code. */ else { mcode->flags |= ITCL_IMPLEMENT_TCL; } *mcodePtr = mcode; return TCL_OK; } /* * ------------------------------------------------------------------------ * Itcl_DeleteMemberCode() * * Destroys all data associated with the given command implementation. * Invoked automatically by Itcl_ReleaseData() when the implementation * is no longer being used. * ------------------------------------------------------------------------ */ void Itcl_DeleteMemberCode(cdata) CONST char* cdata; /* pointer to member function definition */ { ItclMemberCode* mcode = (ItclMemberCode*)cdata; /* * Free the argument list. If empty, free the compiled locals, if any. */ if (mcode->arglist) { Itcl_DeleteArgList(mcode->arglist); } else if (mcode->procPtr && mcode->procPtr->firstLocalPtr) { Itcl_DeleteArgList(mcode->procPtr->firstLocalPtr); } if (mcode->procPtr) { ckfree((char*) mcode->procPtr->cmdPtr); if (mcode->procPtr->bodyPtr) { Tcl_DecrRefCount(mcode->procPtr->bodyPtr); } ckfree((char*)mcode->procPtr); } ckfree((char*)mcode); } /* * ------------------------------------------------------------------------ * Itcl_GetMemberCode() * * Makes sure that the implementation for an [incr Tcl] code body is * ready to run. Note that a member function can be declared without * being defined. The class definition may contain a declaration of * the member function, but its body may be defined in a separate file. * If an undefined function is encountered, this routine automatically * attempts to autoload it. If the body is implemented via Tcl code, * then it is compiled here as well. * * Returns TCL_ERROR (along with an error message in the interpreter) * if an error is encountered, or if the implementation is not defined * and cannot be autoloaded. Returns TCL_OK if implementation is * ready to use. * ------------------------------------------------------------------------ */ int Itcl_GetMemberCode(interp, member) Tcl_Interp* interp; /* interpreter managing this action */ ItclMember* member; /* member containing code body */ { int result; ItclMemberCode *mcode = member->code; assert(mcode != NULL); /* * If the implementation has not yet been defined, try to * autoload it now. */ if (!Itcl_IsMemberCodeImplemented(mcode)) { result = Tcl_VarEval(interp, "::auto_load ", member->fullname, (char*)NULL); if (result != TCL_OK) { char msg[256]; sprintf(msg, "\n (while autoloading code for \"%.100s\")", member->fullname); Tcl_AddErrorInfo(interp, msg); return result; } Tcl_ResetResult(interp); /* get rid of 1/0 status */ } /* * If the implementation is still not available, then * autoloading must have failed. * * TRICKY NOTE: If code has been autoloaded, then the * old mcode pointer is probably invalid. Go back to * the member and look at the current code pointer again. */ mcode = member->code; assert(mcode != NULL); if (!Itcl_IsMemberCodeImplemented(mcode)) { Tcl_AppendResult(interp, "member function \"", member->fullname, "\" is not defined and cannot be autoloaded", (char*)NULL); return TCL_ERROR; } /* * If the member is a constructor and the class has an * initialization command, compile it here. */ if ((member->flags & ITCL_CONSTRUCTOR) != 0 && (member->classDefn->initCode != NULL)) { result = TclProcCompileProc(interp, mcode->procPtr, member->classDefn->initCode, (Namespace*)member->classDefn->namesp, "initialization code for", member->fullname); if (result != TCL_OK) { return result; } } /* * If the code body has a Tcl implementation, then compile it here. */ if ((mcode->flags & ITCL_IMPLEMENT_TCL) != 0) { result = TclProcCompileProc(interp, mcode->procPtr, mcode->procPtr->bodyPtr, (Namespace*)member->classDefn->namesp, "body for", member->fullname); if (result != TCL_OK) { return result; } } return TCL_OK; } /* * ------------------------------------------------------------------------ * Itcl_EvalMemberCode() * * Used to execute an ItclMemberCode representation of a code * fragment. This code may be a body of Tcl commands, or a C handler * procedure. * * Executes the command with the given arguments (objc,objv) and * returns an integer status code (TCL_OK/TCL_ERROR). Returns the * result string or an error message in the interpreter. * ------------------------------------------------------------------------ */ int Itcl_EvalMemberCode(interp, mfunc, member, contextObj, objc, objv) Tcl_Interp *interp; /* current interpreter */ ItclMemberFunc *mfunc; /* member func, or NULL (for error messages) */ ItclMember *member; /* command member containing code */ ItclObject *contextObj; /* object context, or NULL */ int objc; /* number of arguments */ Tcl_Obj *CONST objv[]; /* argument objects */ { int result = TCL_OK; Itcl_CallFrame *oldFramePtr = NULL; int i, transparent, newEntry; ItclObjectInfo *info; ItclMemberCode *mcode; ItclContext context; Itcl_CallFrame *framePtr, *transFramePtr; /* * If this code does not have an implementation yet, then * try to autoload one. Also, if this is Tcl code, make sure * that it's compiled and ready to use. */ if (Itcl_GetMemberCode(interp, member) != TCL_OK) { return TCL_ERROR; } mcode = member->code; /* * Bump the reference count on this code, in case it is * redefined or deleted during execution. */ Itcl_PreserveData((ClientData)mcode); /* * Install a new call frame context for the current code. * If the current call frame is marked as "transparent", then * do an "uplevel" operation to move past it. Transparent * call frames are installed by Itcl_HandleInstance. They * provide a way of entering an object context without * interfering with the normal call stack. */ transparent = 0; info = member->classDefn->info; framePtr = _Tcl_GetCallFrame(interp, 0); for (i = Itcl_GetStackSize(&info->transparentFrames)-1; i >= 0; i--) { transFramePtr = (Itcl_CallFrame*) Itcl_GetStackValue(&info->transparentFrames, i); if (framePtr == transFramePtr) { transparent = 1; break; } } if (transparent) { framePtr = _Tcl_GetCallFrame(interp, 1); oldFramePtr = _Tcl_ActivateCallFrame(interp, framePtr); } if (Itcl_PushContext(interp, member, member->classDefn, contextObj, &context) != TCL_OK) { return TCL_ERROR; } /* * If this is a method with a Tcl implementation, or a * constructor with initCode, then parse its arguments now. */ if (mfunc && objc > 0) { if ((mcode->flags & ITCL_IMPLEMENT_TCL) != 0 || ( (member->flags & ITCL_CONSTRUCTOR) != 0 && (member->classDefn->initCode != NULL) ) ) { if (Itcl_AssignArgs(interp, objc, objv, mfunc) != TCL_OK) { result = TCL_ERROR; goto evalMemberCodeDone; } } } /* * If this code is a constructor, and if it is being invoked * when an object is first constructed (i.e., the "constructed" * table is still active within the object), then handle the * "initCode" associated with the constructor and make sure that * all base classes are properly constructed. * * TRICKY NOTE: * The "initCode" must be executed here. This is the only * opportunity where the arguments of the constructor are * available in a call frame. */ if ((member->flags & ITCL_CONSTRUCTOR) && contextObj && contextObj->constructed) { result = Itcl_ConstructBase(interp, contextObj, member->classDefn); if (result != TCL_OK) { goto evalMemberCodeDone; } } /* * Execute the code body... */ if ((mcode->flags & ITCL_IMPLEMENT_OBJCMD) != 0) { result = (*mcode->cfunc.objCmd)(mcode->clientData, interp, objc, objv); } else if ((mcode->flags & ITCL_IMPLEMENT_ARGCMD) != 0) { CONST char **argv; argv = (CONST char**)ckalloc( (unsigned)(objc*sizeof(char*)) ); for (i=0; i < objc; i++) { argv[i] = Tcl_GetStringFromObj(objv[i], (int*)NULL); } result = (*mcode->cfunc.argCmd)(mcode->clientData, interp, objc, argv); ckfree((char*)argv); } else if ((mcode->flags & ITCL_IMPLEMENT_TCL) != 0) { result = Tcl_EvalObj(interp, mcode->procPtr->bodyPtr); } else { Tcl_Panic("itcl: bad implementation flag for %s", member->fullname); } /* * If this is a constructor or destructor, and if it is being * invoked at the appropriate time, keep track of which methods * have been called. This information is used to implicitly * invoke constructors/destructors as needed. */ if ((member->flags & ITCL_DESTRUCTOR) && contextObj && contextObj->destructed) { Tcl_CreateHashEntry(contextObj->destructed, member->classDefn->fullname, &newEntry); } if ((member->flags & ITCL_CONSTRUCTOR) && contextObj && contextObj->constructed) { Tcl_CreateHashEntry(contextObj->constructed, member->classDefn->name, &newEntry); } evalMemberCodeDone: Itcl_PopContext(interp, &context); if (transparent) { (void) _Tcl_ActivateCallFrame(interp, oldFramePtr); } Itcl_ReleaseData((ClientData)mcode); return result; } /* * ------------------------------------------------------------------------ * Itcl_CreateArgList() * * Parses a Tcl list representing an argument declaration and returns * a linked list of CompiledLocal values. Usually invoked as part * of Itcl_CreateMemberFunc() when a new method or procedure is being * defined. * ------------------------------------------------------------------------ */ int Itcl_CreateArgList(interp, decl, argcPtr, argPtr) Tcl_Interp* interp; /* interpreter managing this function */ CONST char* decl; /* string representing argument list */ int* argcPtr; /* returns number of args in argument list */ CompiledLocal** argPtr; /* returns pointer to parsed argument list */ { int status = TCL_OK; /* assume that this will succeed */ int i, argc, fargc; CONST char **argv, **fargv; CompiledLocal *localPtr, *last; *argPtr = last = NULL; *argcPtr = 0; if (decl) { if (Tcl_SplitList(interp, decl, &argc, &argv) != TCL_OK) { return TCL_ERROR; } for (i=0; i < argc && status == TCL_OK; i++) { if (Tcl_SplitList(interp, argv[i], &fargc, &fargv) != TCL_OK) { status = TCL_ERROR; } else { localPtr = NULL; if (fargc == 0 || *fargv[0] == '\0') { char mesg[100]; sprintf(mesg, "argument #%d has no name", i); Tcl_SetResult(interp, mesg, TCL_VOLATILE); status = TCL_ERROR; } else if (fargc > 2) { Tcl_AppendResult(interp, "too many fields in argument specifier \"", argv[i], "\"", (char*)NULL); status = TCL_ERROR; } else if (strstr(fargv[0],"::")) { Tcl_AppendResult(interp, "bad argument name \"", fargv[0], "\"", (char*)NULL); status = TCL_ERROR; } else if (fargc == 1) { localPtr = Itcl_CreateArg(fargv[0], (char*)NULL); } else { localPtr = Itcl_CreateArg(fargv[0], fargv[1]); } if (localPtr) { localPtr->frameIndex = i; if (*argPtr == NULL) { *argPtr = last = localPtr; } else { last->nextPtr = localPtr; last = localPtr; } } } ckfree((char*)fargv); } ckfree((char*)argv); } /* * If anything went wrong, destroy whatever arguments were * created and return an error. */ if (status == TCL_OK) { *argcPtr = argc; } else { Itcl_DeleteArgList(*argPtr); *argPtr = NULL; } return status; } /* * ------------------------------------------------------------------------ * Itcl_CreateArg() * * Creates a new Tcl Arg structure and fills it with the given * information. Returns a pointer to the new Arg structure. * ------------------------------------------------------------------------ */ CompiledLocal* Itcl_CreateArg(name, init) CONST char* name; /* name of new argument */ CONST char* init; /* initial value */ { CompiledLocal *localPtr = NULL; int nameLen; if (name == NULL) { name = ""; } nameLen = strlen(name); localPtr = (CompiledLocal*)ckalloc( (unsigned)(sizeof(CompiledLocal)-sizeof(localPtr->name) + nameLen+1) ); localPtr->nextPtr = NULL; localPtr->nameLength = nameLen; localPtr->frameIndex = 0; /* set this later */ ItclInitVarArgument(localPtr); localPtr->resolveInfo = NULL; if (init != NULL) { localPtr->defValuePtr = Tcl_NewStringObj(init, -1); Tcl_IncrRefCount(localPtr->defValuePtr); } else { localPtr->defValuePtr = NULL; } strcpy(localPtr->name, name); return localPtr; } /* * ------------------------------------------------------------------------ * Itcl_DeleteArgList() * * Destroys a chain of arguments acting as an argument list. Usually * invoked when a method/proc is being destroyed, to discard its * argument list. * ------------------------------------------------------------------------ */ void Itcl_DeleteArgList(arglist) CompiledLocal *arglist; /* first argument in arg list chain */ { CompiledLocal *localPtr, *next; for (localPtr=arglist; localPtr; localPtr=next) { if (localPtr->defValuePtr != NULL) { Tcl_DecrRefCount(localPtr->defValuePtr); } if (localPtr->resolveInfo) { if (localPtr->resolveInfo->deleteProc) { localPtr->resolveInfo->deleteProc(localPtr->resolveInfo); } else { ckfree((char*)localPtr->resolveInfo); } localPtr->resolveInfo = NULL; } next = localPtr->nextPtr; ckfree((char*)localPtr); } } /* * ------------------------------------------------------------------------ * Itcl_ArgList() * * Returns a Tcl_Obj containing the string representation for the * given argument list. This object has a reference count of 1. * The reference count should be decremented when the string is no * longer needed, and it will free itself. * ------------------------------------------------------------------------ */ Tcl_Obj* Itcl_ArgList(argc, arglist) int argc; /* number of arguments */ CompiledLocal* arglist; /* first argument in arglist */ { char *val; Tcl_Obj *objPtr; Tcl_DString buffer; Tcl_DStringInit(&buffer); while (arglist && argc-- > 0) { if (arglist->defValuePtr) { val = Tcl_GetStringFromObj(arglist->defValuePtr, (int*)NULL); Tcl_DStringStartSublist(&buffer); Tcl_DStringAppendElement(&buffer, arglist->name); Tcl_DStringAppendElement(&buffer, val); Tcl_DStringEndSublist(&buffer); } else { Tcl_DStringAppendElement(&buffer, arglist->name); } arglist = arglist->nextPtr; } objPtr = Tcl_NewStringObj(Tcl_DStringValue(&buffer), Tcl_DStringLength(&buffer)); Tcl_DStringFree(&buffer); return objPtr; } /* * ------------------------------------------------------------------------ * Itcl_EquivArgLists() * * Compares two argument lists to see if they are equivalent. The * first list is treated as a prototype, and the second list must * match it. Argument names may be different, but they must match in * meaning. If one argument is optional, the corresponding argument * must also be optional. If the prototype list ends with the magic * "args" argument, then it matches everything in the other list. * * Returns non-zero if the argument lists are equivalent. * ------------------------------------------------------------------------ */ int Itcl_EquivArgLists(arg1, arg1c, arg2, arg2c) CompiledLocal* arg1; /* prototype argument list */ int arg1c; /* number of args in prototype arg list */ CompiledLocal* arg2; /* another argument list to match against */ int arg2c; /* number of args in matching list */ { char *dval1, *dval2; while (arg1 && arg1c > 0 && arg2 && arg2c > 0) { /* * If the prototype argument list ends with the magic "args" * argument, then it matches everything in the other list. */ if (arg1c == 1 && strcmp(arg1->name,"args") == 0) { return 1; } /* * If one has a default value, then the other must have the * same default value. */ if (arg1->defValuePtr) { if (arg2->defValuePtr == NULL) { return 0; } dval1 = Tcl_GetStringFromObj(arg1->defValuePtr, (int*)NULL); dval2 = Tcl_GetStringFromObj(arg2->defValuePtr, (int*)NULL); if (strcmp(dval1, dval2) != 0) { return 0; } } else if (arg2->defValuePtr) { return 0; } arg1 = arg1->nextPtr; arg1c--; arg2 = arg2->nextPtr; arg2c--; } if (arg1c == 1 && strcmp(arg1->name,"args") == 0) { return 1; } return (arg1c == 0 && arg2c == 0); } /* * ------------------------------------------------------------------------ * Itcl_GetMemberFuncUsage() * * Returns a string showing how a command member should be invoked. * If the command member is a method, then the specified object name * is reported as part of the invocation path: * * obj method arg ?arg arg ...? * * Otherwise, the "obj" pointer is ignored, and the class name is * used as the invocation path: * * class::proc arg ?arg arg ...? * * Returns the string by appending it onto the Tcl_Obj passed in as * an argument. * ------------------------------------------------------------------------ */ void Itcl_GetMemberFuncUsage(mfunc, contextObj, objPtr) ItclMemberFunc *mfunc; /* command member being examined */ ItclObject *contextObj; /* invoked with respect to this object */ Tcl_Obj *objPtr; /* returns: string showing usage */ { int argcount; char *name; CompiledLocal *arglist, *argPtr; Tcl_HashEntry *entry; ItclMemberFunc *mf; ItclClass *cdefnPtr; /* * If the command is a method and an object context was * specified, then add the object context. If the method * was a constructor, and if the object is being created, * then report the invocation via the class creation command. */ if ((mfunc->member->flags & ITCL_COMMON) == 0) { if ((mfunc->member->flags & ITCL_CONSTRUCTOR) != 0 && contextObj->constructed) { cdefnPtr = (ItclClass*)contextObj->classDefn; mf = NULL; entry = Tcl_FindHashEntry(&cdefnPtr->resolveCmds, "constructor"); if (entry) { mf = (ItclMemberFunc*)Tcl_GetHashValue(entry); } if (mf == mfunc) { Tcl_GetCommandFullName(contextObj->classDefn->interp, contextObj->classDefn->accessCmd, objPtr); Tcl_AppendToObj(objPtr, " ", -1); name = (char *) Tcl_GetCommandName( contextObj->classDefn->interp, contextObj->accessCmd); Tcl_AppendToObj(objPtr, name, -1); } else { Tcl_AppendToObj(objPtr, mfunc->member->fullname, -1); } } else if (contextObj && contextObj->accessCmd) { name = (char *) Tcl_GetCommandName(contextObj->classDefn->interp, contextObj->accessCmd); Tcl_AppendStringsToObj(objPtr, name, " ", mfunc->member->name, (char*)NULL); } else { Tcl_AppendStringsToObj(objPtr, " ", mfunc->member->name, (char*)NULL); } } else { Tcl_AppendToObj(objPtr, mfunc->member->fullname, -1); } /* * Add the argument usage info. */ if (mfunc->member->code) { arglist = mfunc->member->code->arglist; argcount = mfunc->member->code->argcount; } else if (mfunc->arglist) { arglist = mfunc->arglist; argcount = mfunc->argcount; } else { arglist = NULL; argcount = 0; } if (arglist) { for (argPtr=arglist; argPtr && argcount > 0; argPtr=argPtr->nextPtr, argcount--) { if (argcount == 1 && strcmp(argPtr->name, "args") == 0) { Tcl_AppendToObj(objPtr, " ?arg arg ...?", -1); } else if (argPtr->defValuePtr) { Tcl_AppendStringsToObj(objPtr, " ?", argPtr->name, "?", (char*)NULL); } else { Tcl_AppendStringsToObj(objPtr, " ", argPtr->name, (char*)NULL); } } } } /* * ------------------------------------------------------------------------ * Itcl_ExecMethod() * * Invoked by Tcl to handle the execution of a user-defined method. * A method is similar to the usual Tcl proc, but has access to * object-specific data. If for some reason there is no current * object context, then a method call is inappropriate, and an error * is returned. * * Methods are implemented either as Tcl code fragments, or as C-coded * procedures. For Tcl code fragments, command arguments are parsed * according to the argument list, and the body is executed in the * scope of the class where it was defined. For C procedures, the * arguments are passed in "as-is", and the procedure is executed in * the most-specific class scope. * ------------------------------------------------------------------------ */ int Itcl_ExecMethod(clientData, interp, objc, objv) ClientData clientData; /* method definition */ Tcl_Interp *interp; /* current interpreter */ int objc; /* number of arguments */ Tcl_Obj *CONST objv[]; /* argument objects */ { ItclMemberFunc *mfunc = (ItclMemberFunc*)clientData; ItclMember *member = mfunc->member; int result = TCL_OK; char *token; Tcl_HashEntry *entry; ItclClass *contextClass; ItclObject *contextObj; /* * Make sure that the current namespace context includes an * object that is being manipulated. Methods can be executed * only if an object context exists. */ if (Itcl_GetContext(interp, &contextClass, &contextObj) != TCL_OK) { return TCL_ERROR; } if (contextObj == NULL) { Tcl_AppendResult(interp, "cannot access object-specific info without an object context", (char*)NULL); return TCL_ERROR; } /* * Make sure that this command member can be accessed from * the current namespace context. */ if (mfunc->member->protection != ITCL_PUBLIC) { Tcl_Namespace *contextNs = Itcl_GetTrueNamespace(interp, contextClass->info); if (!Itcl_CanAccessFunc(mfunc, contextNs)) { Tcl_AppendResult(interp, "can't access \"", member->fullname, "\": ", Itcl_ProtectionStr(member->protection), " function", (char*)NULL); return TCL_ERROR; } } /* * All methods should be "virtual" unless they are invoked with * a "::" scope qualifier. * * To implement the "virtual" behavior, find the most-specific * implementation for the method by looking in the "resolveCmds" * table for this class. */ token = Tcl_GetStringFromObj(objv[0], (int*)NULL); if (strstr(token, "::") == NULL) { entry = Tcl_FindHashEntry(&contextObj->classDefn->resolveCmds, member->name); if (entry) { mfunc = (ItclMemberFunc*)Tcl_GetHashValue(entry); member = mfunc->member; } } /* * Execute the code for the method. Be careful to protect * the method in case it gets deleted during execution. */ Itcl_PreserveData((ClientData)mfunc); result = Itcl_EvalMemberCode(interp, mfunc, member, contextObj, objc, objv); result = Itcl_ReportFuncErrors(interp, mfunc, contextObj, result); Itcl_ReleaseData((ClientData)mfunc); return result; } /* * ------------------------------------------------------------------------ * Itcl_ExecProc() * * Invoked by Tcl to handle the execution of a user-defined proc. * * Procs are implemented either as Tcl code fragments, or as C-coded * procedures. For Tcl code fragments, command arguments are parsed * according to the argument list, and the body is executed in the * scope of the class where it was defined. For C procedures, the * arguments are passed in "as-is", and the procedure is executed in * the most-specific class scope. * ------------------------------------------------------------------------ */ int Itcl_ExecProc(clientData, interp, objc, objv) ClientData clientData; /* proc definition */ Tcl_Interp *interp; /* current interpreter */ int objc; /* number of arguments */ Tcl_Obj *CONST objv[]; /* argument objects */ { ItclMemberFunc *mfunc = (ItclMemberFunc*)clientData; ItclMember *member = mfunc->member; int result = TCL_OK; /* * Make sure that this command member can be accessed from * the current namespace context. */ if (mfunc->member->protection != ITCL_PUBLIC) { Tcl_Namespace *contextNs = Itcl_GetTrueNamespace(interp, mfunc->member->classDefn->info); if (!Itcl_CanAccessFunc(mfunc, contextNs)) { Tcl_AppendResult(interp, "can't access \"", member->fullname, "\": ", Itcl_ProtectionStr(member->protection), " function", (char*)NULL); return TCL_ERROR; } } /* * Execute the code for the proc. Be careful to protect * the proc in case it gets deleted during execution. */ Itcl_PreserveData((ClientData)mfunc); result = Itcl_EvalMemberCode(interp, mfunc, member, (ItclObject*)NULL, objc, objv); result = Itcl_ReportFuncErrors(interp, mfunc, (ItclObject*)NULL, result); Itcl_ReleaseData((ClientData)mfunc); return result; } /* * ------------------------------------------------------------------------ * Itcl_PushContext() * * Sets up the class/object context so that a body of [incr Tcl] * code can be executed. This procedure pushes a call frame with * the proper namespace context for the class. If an object context * is supplied, the object's instance variables are integrated into * the call frame so they can be accessed as local variables. * ------------------------------------------------------------------------ */ int Itcl_PushContext(interp, member, contextClass, contextObj, contextPtr) Tcl_Interp *interp; /* interpreter managing this body of code */ ItclMember *member; /* member containing code body */ ItclClass *contextClass; /* class context */ ItclObject *contextObj; /* object context, or NULL */ ItclContext *contextPtr; /* storage space for class/object context */ { ItclCallFrame *framePtr = (ItclCallFrame *) &contextPtr->frame; int result, localCt, newEntry; ItclMemberCode *mcode; Proc *procPtr; Tcl_HashEntry *entry; /* * Activate the call frame. If this fails, we'll bail out * before allocating any resources. * * NOTE: Always push a call frame that looks like a proc. * This causes global variables to be handled properly * inside methods/procs. */ result = Tcl_PushCallFrame(interp, (Tcl_CallFrame*)framePtr, contextClass->namesp, /* isProcCallFrame */ 1); if (result != TCL_OK) { return result; } contextPtr->classDefn = contextClass; contextPtr->compiledLocals = &contextPtr->localStorage[0]; /* * If this is an object context, register it in a hash table * of all known contexts. We'll need this later if we * call Itcl_GetContext to get the object context for the * current call frame. */ if (contextObj) { entry = Tcl_CreateHashEntry(&contextClass->info->contextFrames, (char*)framePtr, &newEntry); Itcl_PreserveData((ClientData)contextObj); Tcl_SetHashValue(entry, (ClientData)contextObj); } /* * Set up the compiled locals in the call frame and assign * argument variables. */ if (member) { mcode = member->code; procPtr = mcode->procPtr; /* * Invoking TclInitCompiledLocals with a framePtr->procPtr->bodyPtr * that is not a compiled byte code type leads to a crash. So * make sure that the body is compiled here. This needs to * be done even if the body of the Itcl method is not implemented * as a Tcl proc or has no implementation. The empty string should * have been defined as the body if no implementation was defined. */ assert(mcode->procPtr->bodyPtr != NULL); result = TclProcCompileProc(interp, mcode->procPtr, mcode->procPtr->bodyPtr, (Namespace*)member->classDefn->namesp, "body for", member->fullname); if (result != TCL_OK) { return result; } /* * If there are too many compiled locals to fit in the default * storage space for the context, then allocate more space. */ localCt = procPtr->numCompiledLocals; if (localCt > (int)(sizeof(contextPtr->localStorage)/itclVarLocalSize)) { contextPtr->compiledLocals = (Var*)ckalloc( (unsigned)(localCt * itclVarLocalSize) ); } /* * Initialize and resolve compiled variable references. * Class variables will 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. */ framePtr->procPtr = procPtr; framePtr->numCompiledLocals = localCt; framePtr->compiledLocals = contextPtr->compiledLocals; TclInitCompiledLocals(interp, (CallFrame *) framePtr, (Namespace*)contextClass->namesp); } return result; } /* * ------------------------------------------------------------------------ * Itcl_PopContext() * * Removes a class/object context previously set up by Itcl_PushContext. * Usually called after an [incr Tcl] code body has been executed, * to clean up. * ------------------------------------------------------------------------ */ void Itcl_PopContext(interp, contextPtr) Tcl_Interp *interp; /* interpreter managing this body of code */ ItclContext *contextPtr; /* storage space for class/object context */ { Itcl_CallFrame *framePtr; ItclObjectInfo *info; ItclObject *contextObj; Tcl_HashEntry *entry; /* * See if the current call frame has an object context * associated with it. If so, release the claim on the * object info. */ framePtr = _Tcl_GetCallFrame(interp, 0); info = contextPtr->classDefn->info; entry = Tcl_FindHashEntry(&info->contextFrames, (char*)framePtr); if (entry != NULL) { contextObj = (ItclObject*)Tcl_GetHashValue(entry); Itcl_ReleaseData((ClientData)contextObj); Tcl_DeleteHashEntry(entry); } /* * Remove the call frame. */ Tcl_PopCallFrame(interp); /* * Free the compiledLocals array if malloc'ed storage was used. */ if (contextPtr->compiledLocals != &contextPtr->localStorage[0]) { ckfree((char*)contextPtr->compiledLocals); } } /* * ------------------------------------------------------------------------ * Itcl_GetContext() * * Convenience routine for looking up the current object/class context. * Useful in implementing methods/procs to see what class, and perhaps * what object, is active. * * Returns TCL_OK if the current namespace is a class namespace. * Also returns pointers to the class definition, and to object * data if an object context is active. Returns TCL_ERROR (along * with an error message in the interpreter) if a class namespace * is not active. * ------------------------------------------------------------------------ */ int Itcl_GetContext(interp, cdefnPtr, odefnPtr) Tcl_Interp *interp; /* current interpreter */ ItclClass **cdefnPtr; /* returns: class definition or NULL */ ItclObject **odefnPtr; /* returns: object data or NULL */ { Tcl_Namespace *activeNs = Tcl_GetCurrentNamespace(interp); ItclObjectInfo *info; Itcl_CallFrame *framePtr; Tcl_HashEntry *entry; /* * Return NULL for anything that cannot be found. */ *cdefnPtr = NULL; *odefnPtr = NULL; /* * If the active namespace is a class namespace, then return * all known info. See if the current call frame is a known * object context, and if so, return that context. */ if (Itcl_IsClassNamespace(activeNs)) { *cdefnPtr = (ItclClass*)activeNs->clientData; framePtr = _Tcl_GetCallFrame(interp, 0); info = (*cdefnPtr)->info; entry = Tcl_FindHashEntry(&info->contextFrames, (char*)framePtr); if (entry != NULL) { *odefnPtr = (ItclObject*)Tcl_GetHashValue(entry); } return TCL_OK; } /* * If there is no class/object context, return an error message. */ Tcl_AppendResult(interp, "namespace \"", activeNs->fullName, "\" is not a class namespace", (char*)NULL); return TCL_ERROR; } /* * ------------------------------------------------------------------------ * Itcl_AssignArgs() * * Matches a list of arguments against a Tcl argument specification. * Supports all of the rules regarding arguments for Tcl procs, including * default arguments and variable-length argument lists. * * Assumes that a local call frame is already installed. As variables * are successfully matched, they are stored as variables in the call * frame. Returns TCL_OK on success, or TCL_ERROR (along with an error * message in interp->result) on error. * ------------------------------------------------------------------------ */ int Itcl_AssignArgs(interp, objc, objv, mfunc) Tcl_Interp *interp; /* interpreter */ int objc; /* number of arguments */ Tcl_Obj *CONST objv[]; /* argument objects */ ItclMemberFunc *mfunc; /* member function info (for error messages) */ { ItclMemberCode *mcode = mfunc->member->code; int result = TCL_OK; int defargc; CONST char **defargv = NULL; Tcl_Obj **defobjv = NULL; int configc = 0; ItclVarDefn **configVars = NULL; char **configVals = NULL; int vi, argsLeft; ItclClass *contextClass; ItclObject *contextObj; CompiledLocal *argPtr; ItclCallFrame *framePtr; Var *varPtr; Tcl_Obj *objPtr, *listPtr; char *value; framePtr = (ItclCallFrame *) _Tcl_GetCallFrame(interp, 0); framePtr->objc = objc; framePtr->objv = objv; /* ref counts for args are incremented below */ /* * See if there is a current object context. We may need * it later on. */ (void) Itcl_GetContext(interp, &contextClass, &contextObj); Tcl_ResetResult(interp); /* * Match the actual arguments against the procedure's formal * parameters to compute local variables. */ varPtr = framePtr->compiledLocals; for (argsLeft=mcode->argcount, argPtr=mcode->arglist, objv++, objc--; argsLeft > 0; argPtr=argPtr->nextPtr, argsLeft--, ItclNextLocal(varPtr), objv++, objc--) { if (!TclIsVarArgument(argPtr)) { Tcl_Panic("local variable %s is not argument but should be", argPtr->name); return TCL_ERROR; } if (TclIsVarTemporary(argPtr)) { Tcl_Panic("local variable is temporary but should be an argument"); 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 ((argsLeft == 1) && (strcmp(argPtr->name, "args") == 0)) { if (objc < 0) objc = 0; listPtr = Tcl_NewListObj(objc, objv); ItclVarObjValue(varPtr) = listPtr; Tcl_IncrRefCount(listPtr); /* local var is a reference */ ItclClearVarUndefined(varPtr); objc = 0; break; } /* * Handle the special case of the last formal being "config". * When it occurs, treat all remaining arguments as public * variable assignments. Set the local "config" variable * to the list of public variables assigned. */ else if ( (argsLeft == 1) && (strcmp(argPtr->name, "config") == 0) && contextObj ) { /* * If this is not an old-style method, discourage against * the use of the "config" argument. */ if ((mfunc->member->flags & ITCL_OLD_STYLE) == 0) { Tcl_AppendResult(interp, "\"config\" argument is an anachronism\n", "[incr Tcl] no longer supports the \"config\" argument.\n", "Instead, use the \"args\" argument and then use the\n", "built-in configure method to handle args like this:\n", " eval configure $args", (char*)NULL); result = TCL_ERROR; goto argErrors; } /* * Otherwise, handle the "config" argument in the usual way... * - parse all "-name value" assignments * - set "config" argument to the list of variable names */ if (objc > 0) { /* still have some arguments left? */ result = ItclParseConfig(interp, objc, objv, contextObj, &configc, &configVars, &configVals); if (result != TCL_OK) { goto argErrors; } listPtr = Tcl_NewListObj(0, (Tcl_Obj**)NULL); for (vi=0; vi < configc; vi++) { objPtr = Tcl_NewStringObj( configVars[vi]->member->classDefn->name, -1); Tcl_AppendToObj(objPtr, "::", -1); Tcl_AppendToObj(objPtr, configVars[vi]->member->name, -1); Tcl_ListObjAppendElement(interp, listPtr, objPtr); } ItclVarObjValue(varPtr) = listPtr; Tcl_IncrRefCount(listPtr); /* local var is a reference */ ItclClearVarUndefined(varPtr); objc = 0; /* all remaining args handled */ } else if (argPtr->defValuePtr) { value = Tcl_GetStringFromObj(argPtr->defValuePtr, (int*)NULL); result = Tcl_SplitList(interp, value, &defargc, &defargv); if (result != TCL_OK) { goto argErrors; } defobjv = (Tcl_Obj**)ckalloc( (unsigned)(defargc*sizeof(Tcl_Obj*)) ); for (vi=0; vi < defargc; vi++) { objPtr = Tcl_NewStringObj(defargv[vi], -1); Tcl_IncrRefCount(objPtr); defobjv[vi] = objPtr; } result = ItclParseConfig(interp, defargc, defobjv, contextObj, &configc, &configVars, &configVals); if (result != TCL_OK) { goto argErrors; } listPtr = Tcl_NewListObj(0, (Tcl_Obj**)NULL); for (vi=0; vi < configc; vi++) { objPtr = Tcl_NewStringObj( configVars[vi]->member->classDefn->name, -1); Tcl_AppendToObj(objPtr, "::", -1); Tcl_AppendToObj(objPtr, configVars[vi]->member->name, -1); Tcl_ListObjAppendElement(interp, listPtr, objPtr); } ItclVarObjValue(varPtr) = listPtr; Tcl_IncrRefCount(listPtr); /* local var is a reference */ ItclClearVarUndefined(varPtr); } else { objPtr = Tcl_NewStringObj("", 0); ItclVarObjValue(varPtr) = objPtr; Tcl_IncrRefCount(objPtr); /* local var is a reference */ ItclClearVarUndefined(varPtr); } } /* * Resume the usual processing of arguments... */ else if (objc > 0) { /* take next arg as value */ objPtr = *objv; ItclVarObjValue(varPtr) = objPtr; ItclClearVarUndefined(varPtr); Tcl_IncrRefCount(objPtr); /* local var is a reference */ } else if (argPtr->defValuePtr) { /* ...or use default value */ objPtr = argPtr->defValuePtr; ItclVarObjValue(varPtr) = objPtr; ItclClearVarUndefined(varPtr); Tcl_IncrRefCount(objPtr); /* local var is a reference */ } else { if (mfunc) { objPtr = Tcl_GetObjResult(interp); Tcl_AppendToObj(objPtr, "wrong # args: should be \"", -1); Itcl_GetMemberFuncUsage(mfunc, contextObj, objPtr); Tcl_AppendToObj(objPtr, "\"", -1); } else { Tcl_AppendResult(interp, "no value given for parameter \"", argPtr->name, "\"", (char*)NULL); } result = TCL_ERROR; goto argErrors; } } if (objc > 0) { if (mfunc) { objPtr = Tcl_GetObjResult(interp); Tcl_AppendToObj(objPtr, "wrong # args: should be \"", -1); Itcl_GetMemberFuncUsage(mfunc, contextObj, objPtr); Tcl_AppendToObj(objPtr, "\"", -1); } else { Tcl_AppendResult(interp, "too many arguments", (char*)NULL); } result = TCL_ERROR; goto argErrors; } /* * Handle any "config" assignments. */ if (configc > 0) { if (ItclHandleConfig(interp, configc, configVars, configVals, contextObj) != TCL_OK) { result = TCL_ERROR; goto argErrors; } } /* * All arguments were successfully matched. */ result = TCL_OK; /* * If any errors were found, clean up and return error status. */ argErrors: if (defobjv) { for (vi=0; vi < defargc; vi++) { Tcl_DecrRefCount(defobjv[vi]); } ckfree((char*)defobjv); } if (defargv) { ckfree((char*)defargv); } if (configVars) { ckfree((char*)configVars); } if (configVals) { ckfree((char*)configVals); } return result; } /* * ------------------------------------------------------------------------ * ItclParseConfig() * * Parses a set of arguments as "-variable value" assignments. * Interprets all variable names in the most-specific class scope, * so that an inherited method with a "config" parameter will work * correctly. Returns a list of public variable names and their * corresponding values; both lists should passed to ItclHandleConfig() * to perform assignments, and freed when no longer in use. Returns a * status TCL_OK/TCL_ERROR and returns error messages in the interpreter. * ------------------------------------------------------------------------ */ static int ItclParseConfig(interp, objc, objv, contextObj, rargc, rvars, rvals) Tcl_Interp *interp; /* interpreter */ int objc; /* number of arguments */ Tcl_Obj *CONST objv[]; /* argument objects */ ItclObject *contextObj; /* object whose public vars are being config'd */ int *rargc; /* return: number of variables accessed */ ItclVarDefn ***rvars; /* return: list of variables */ char ***rvals; /* return: list of values */ { int result = TCL_OK; ItclVarLookup *vlookup; Tcl_HashEntry *entry; char *varName, *value; if (objc < 0) objc = 0; *rargc = 0; *rvars = (ItclVarDefn**)ckalloc((unsigned)(objc*sizeof(ItclVarDefn*))); *rvals = (char**)ckalloc((unsigned)(objc*sizeof(char*))); while (objc-- > 0) { /* * Next argument should be "-variable" */ varName = Tcl_GetStringFromObj(*objv, (int*)NULL); if (*varName != '-') { Tcl_AppendResult(interp, "syntax error in config assignment \"", varName, "\": should be \"-variable value\"", (char*)NULL); result = TCL_ERROR; break; } else if (objc-- <= 0) { Tcl_AppendResult(interp, "syntax error in config assignment \"", varName, "\": should be \"-variable value\" (missing value)", (char*)NULL); result = TCL_ERROR; break; } entry = Tcl_FindHashEntry(&contextObj->classDefn->resolveVars, varName+1); if (entry) { vlookup = (ItclVarLookup*)Tcl_GetHashValue(entry); value = Tcl_GetStringFromObj(*(objv+1), (int*)NULL); (*rvars)[*rargc] = vlookup->vdefn; /* variable definition */ (*rvals)[*rargc] = value; /* config value */ (*rargc)++; objv += 2; } else { Tcl_AppendResult(interp, "syntax error in config assignment \"", varName, "\": unrecognized variable", (char*)NULL); result = TCL_ERROR; break; } } return result; } /* * ------------------------------------------------------------------------ * ItclHandleConfig() * * Handles the assignment of "config" values to public variables. * The list of assignments is parsed in ItclParseConfig(), but the * actual assignments are performed here. If the variables have any * associated "config" code, it is invoked here as well. If errors * are detected during assignment or "config" code execution, the * variable is set back to its previous value and an error is returned. * * Returns a status TCL_OK/TCL_ERROR, and returns any error messages * in the given interpreter. * ------------------------------------------------------------------------ */ static int ItclHandleConfig(interp, argc, vars, vals, contextObj) Tcl_Interp *interp; /* interpreter currently in control */ int argc; /* number of assignments */ ItclVarDefn **vars; /* list of public variable definitions */ char **vals; /* list of public variable values */ ItclObject *contextObj; /* object whose public vars are being config'd */ { int result = TCL_OK; int i; CONST char *val; Tcl_DString lastval; ItclContext context; Itcl_CallFrame *oldFramePtr, *uplevelFramePtr; Tcl_DStringInit(&lastval); /* * All "config" assignments are performed in the most-specific * class scope, so that inherited methods with "config" arguments * will work correctly. */ result = Itcl_PushContext(interp, (ItclMember*)NULL, contextObj->classDefn, contextObj, &context); if (result != TCL_OK) { return TCL_ERROR; } /* * Perform each assignment and execute the "config" code * associated with each variable. If any errors are encountered, * set the variable back to its previous value, and return an error. */ for (i=0; i < argc; i++) { val = Tcl_GetVar2(interp, vars[i]->member->fullname, (char*)NULL, 0); if (!val) { val = ""; } Tcl_DStringSetLength(&lastval, 0); Tcl_DStringAppend(&lastval, val, -1); /* * Set the variable to the specified value. */ if (!Tcl_SetVar2(interp, vars[i]->member->fullname, (char*)NULL, vals[i], 0)) { char msg[256]; sprintf(msg, "\n (while configuring public variable \"%.100s\")", vars[i]->member->fullname); Tcl_AddErrorInfo(interp, msg); result = TCL_ERROR; break; } /* * If the variable has a "config" condition, then execute it. * If it fails, put the variable back the way it was and return * an error. * * TRICKY NOTE: Be careful to evaluate the code one level * up in the call stack, so that it's executed in the * calling context, and not in the context that we've * set up for public variable access. */ if (vars[i]->member->code) { uplevelFramePtr = _Tcl_GetCallFrame(interp, 1); oldFramePtr = _Tcl_ActivateCallFrame(interp, uplevelFramePtr); result = Itcl_EvalMemberCode(interp, (ItclMemberFunc*)NULL, vars[i]->member, contextObj, 0, (Tcl_Obj* CONST*)NULL); (void) _Tcl_ActivateCallFrame(interp, oldFramePtr); if (result != TCL_OK) { char msg[256]; sprintf(msg, "\n (while configuring public variable \"%.100s\")", vars[i]->member->fullname); Tcl_AddErrorInfo(interp, msg); Tcl_SetVar2(interp, vars[i]->member->fullname, (char*)NULL, Tcl_DStringValue(&lastval), 0); break; } } } /* * Clean up and return. */ Itcl_PopContext(interp, &context); Tcl_DStringFree(&lastval); return result; } /* * ------------------------------------------------------------------------ * Itcl_ConstructBase() * * Usually invoked just before executing the body of a constructor * when an object is first created. This procedure makes sure that * all base classes are properly constructed. If an "initCode" fragment * was defined with the constructor for the class, then it is invoked. * After that, the list of base classes is checked for constructors * that are defined but have not yet been invoked. Each of these is * invoked implicitly with no arguments. * * Assumes that a local call frame is already installed, and that * constructor arguments have already been matched and are sitting in * this frame. Returns TCL_OK on success; otherwise, this procedure * returns TCL_ERROR, along with an error message in the interpreter. * ------------------------------------------------------------------------ */ int Itcl_ConstructBase(interp, contextObj, contextClass) Tcl_Interp *interp; /* interpreter */ ItclObject *contextObj; /* object being constructed */ ItclClass *contextClass; /* current class being constructed */ { int result; Itcl_ListElem *elem; ItclClass *cdefn; Tcl_HashEntry *entry; /* * If the class has an "initCode", invoke it in the current context. * * TRICKY NOTE: * This context is the call frame containing the arguments * for the constructor. The "initCode" makes sense right * now--just before the body of the constructor is executed. */ if (contextClass->initCode) { if (Tcl_EvalObj(interp, contextClass->initCode) != TCL_OK) { return TCL_ERROR; } } /* * Scan through the list of base classes and see if any of these * have not been constructed. Invoke base class constructors * implicitly, as needed. Go through the list of base classes * in reverse order, so that least-specific classes are constructed * first. */ elem = Itcl_LastListElem(&contextClass->bases); while (elem) { cdefn = (ItclClass*)Itcl_GetListValue(elem); if (!Tcl_FindHashEntry(contextObj->constructed, cdefn->name)) { result = Itcl_InvokeMethodIfExists(interp, "constructor", cdefn, contextObj, 0, (Tcl_Obj* CONST*)NULL); if (result != TCL_OK) { return TCL_ERROR; } /* * The base class may not have a constructor, but its * own base classes could have one. If the constructor * wasn't found in the last step, then other base classes * weren't constructed either. Make sure that all of its * base classes are properly constructed. */ entry = Tcl_FindHashEntry(&cdefn->functions, "constructor"); if (entry == NULL) { result = Itcl_ConstructBase(interp, contextObj, cdefn); if (result != TCL_OK) { return TCL_ERROR; } } } elem = Itcl_PrevListElem(elem); } return TCL_OK; } /* * ------------------------------------------------------------------------ * Itcl_InvokeMethodIfExists() * * Looks for a particular method in the specified class. If the * method is found, it is invoked with the given arguments. Any * protection level (protected/private) for the method is ignored. * If the method does not exist, this procedure does nothing. * * This procedure is used primarily to invoke the constructor/destructor * when an object is created/destroyed. * * Returns TCL_OK on success; otherwise, this procedure returns * TCL_ERROR along with an error message in the interpreter. * ------------------------------------------------------------------------ */ int Itcl_InvokeMethodIfExists(interp, name, contextClass, contextObj, objc, objv) Tcl_Interp *interp; /* interpreter */ CONST char *name; /* name of desired method */ ItclClass *contextClass; /* current class being constructed */ ItclObject *contextObj; /* object being constructed */ int objc; /* number of arguments */ Tcl_Obj *CONST objv[]; /* argument objects */ { int result = TCL_OK; ItclMemberFunc *mfunc; ItclMember *member; Tcl_HashEntry *entry; Tcl_Obj *cmdlinePtr; int cmdlinec; Tcl_Obj **cmdlinev; /* * Scan through the list of base classes and see if any of these * have not been constructed. Invoke base class constructors * implicitly, as needed. Go through the list of base classes * in reverse order, so that least-specific classes are constructed * first. */ entry = Tcl_FindHashEntry(&contextClass->functions, name); if (entry) { mfunc = (ItclMemberFunc*)Tcl_GetHashValue(entry); member = mfunc->member; /* * Prepend the method name to the list of arguments. */ cmdlinePtr = Itcl_CreateArgs(interp, name, objc, objv); (void) Tcl_ListObjGetElements((Tcl_Interp*)NULL, cmdlinePtr, &cmdlinec, &cmdlinev); /* * Execute the code for the method. Be careful to protect * the method in case it gets deleted during execution. */ Itcl_PreserveData((ClientData)mfunc); result = Itcl_EvalMemberCode(interp, mfunc, member, contextObj, cmdlinec, cmdlinev); result = Itcl_ReportFuncErrors(interp, mfunc, contextObj, result); Itcl_ReleaseData((ClientData)mfunc); Tcl_DecrRefCount(cmdlinePtr); } return result; } /* * ------------------------------------------------------------------------ * Itcl_ReportFuncErrors() * * Used to interpret the status code returned when the body of a * Tcl-style proc is executed. Handles the "errorInfo" and "errorCode" * variables properly, and adds error information into the interpreter * if anything went wrong. Returns a new status code that should be * treated as the return status code for the command. * * This same operation is usually buried in the Tcl InterpProc() * procedure. It is defined here so that it can be reused more easily. * ------------------------------------------------------------------------ */ int Itcl_ReportFuncErrors(interp, mfunc, contextObj, result) Tcl_Interp* interp; /* interpreter being modified */ ItclMemberFunc *mfunc; /* command member that was invoked */ ItclObject *contextObj; /* object context for this command */ int result; /* integer status code from proc body */ { Interp* iPtr = (Interp*)interp; Tcl_Obj *objPtr; char num[20]; if (result != TCL_OK) { if (result == TCL_RETURN) { result = TclUpdateReturnInfo(iPtr); } else if (result == TCL_ERROR) { objPtr = Tcl_NewStringObj("\n ", -1); Tcl_IncrRefCount(objPtr); if (mfunc->member->flags & ITCL_CONSTRUCTOR) { Tcl_AppendToObj(objPtr, "while constructing object \"", -1); Tcl_GetCommandFullName(contextObj->classDefn->interp, contextObj->accessCmd, objPtr); Tcl_AppendToObj(objPtr, "\" in ", -1); Tcl_AppendToObj(objPtr, mfunc->member->fullname, -1); if ((mfunc->member->code->flags & ITCL_IMPLEMENT_TCL) != 0) { Tcl_AppendToObj(objPtr, " (", -1); } } else if (mfunc->member->flags & ITCL_DESTRUCTOR) { Tcl_AppendToObj(objPtr, "while deleting object \"", -1); Tcl_GetCommandFullName(contextObj->classDefn->interp, contextObj->accessCmd, objPtr); Tcl_AppendToObj(objPtr, "\" in ", -1); Tcl_AppendToObj(objPtr, mfunc->member->fullname, -1); if ((mfunc->member->code->flags & ITCL_IMPLEMENT_TCL) != 0) { Tcl_AppendToObj(objPtr, " (", -1); } } else { Tcl_AppendToObj(objPtr, "(", -1); if (contextObj && contextObj->accessCmd) { Tcl_AppendToObj(objPtr, "object \"", -1); Tcl_GetCommandFullName(contextObj->classDefn->interp, contextObj->accessCmd, objPtr); Tcl_AppendToObj(objPtr, "\" ", -1); } if ((mfunc->member->flags & ITCL_COMMON) != 0) { Tcl_AppendToObj(objPtr, "procedure", -1); } else { Tcl_AppendToObj(objPtr, "method", -1); } Tcl_AppendToObj(objPtr, " \"", -1); Tcl_AppendToObj(objPtr, mfunc->member->fullname, -1); Tcl_AppendToObj(objPtr, "\" ", -1); } if ((mfunc->member->code->flags & ITCL_IMPLEMENT_TCL) != 0) { Tcl_AppendToObj(objPtr, "body line ", -1); sprintf(num, "%d", Tcl_GetErrorLine((Tcl_Interp *)iPtr)); Tcl_AppendToObj(objPtr, num, -1); Tcl_AppendToObj(objPtr, ")", -1); } else { Tcl_AppendToObj(objPtr, ")", -1); } Tcl_AddErrorInfo(interp, Tcl_GetStringFromObj(objPtr, (int*)NULL)); Tcl_DecrRefCount(objPtr); } 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; } } return result; }