diff options
Diffstat (limited to 'generic/tclOOMethod.c')
| -rw-r--r-- | generic/tclOOMethod.c | 1783 | 
1 files changed, 1783 insertions, 0 deletions
diff --git a/generic/tclOOMethod.c b/generic/tclOOMethod.c new file mode 100644 index 0000000..61215de --- /dev/null +++ b/generic/tclOOMethod.c @@ -0,0 +1,1783 @@ +/* + * tclOOMethod.c -- + * + *	This file contains code to create and manage methods. + * + * Copyright (c) 2005-2011 by Donal K. Fellows + * + * See the file "license.terms" for information on usage and redistribution of + * this file, and for a DISCLAIMER OF ALL WARRANTIES. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include "tclInt.h" +#include "tclOOInt.h" +#include "tclCompile.h" + +/* + * Structure used to help delay computing names of objects or classes for + * [info frame] until needed, making invokation faster in the normal case. + */ + +struct PNI { +    Tcl_Interp *interp;		/* Interpreter in which to compute the name of +				 * a method. */ +    Tcl_Method method;		/* Method to compute the name of. */ +}; + +/* + * Structure used to contain all the information needed about a call frame + * used in a procedure-like method. + */ + +typedef struct { +    CallFrame *framePtr;	/* Reference to the call frame itself (it's +				 * actually allocated on the Tcl stack). */ +    ProcErrorProc *errProc;	/* The error handler for the body. */ +    Tcl_Obj *nameObj;		/* The "name" of the command. */ +    Command cmd;		/* The command structure. Mostly bogus. */ +    ExtraFrameInfo efi;		/* Extra information used for [info frame]. */ +    Command *oldCmdPtr;		/* Saved cmdPtr so that we can be safe after a +				 * recursive call returns. */ +    struct PNI pni;		/* Specialist information used in the efi +				 * field for this type of call. */ +} PMFrameData; + +/* + * Structure used to pass information about variable resolution to the + * on-the-ground resolvers used when working with resolved compiled variables. + */ + +typedef struct { +    Tcl_ResolvedVarInfo info;	/* "Type" information so that the compiled +				 * variable can be linked to the namespace +				 * variable at the right time. */ +    Tcl_Obj *variableObj;	/* The name of the variable. */ +    Tcl_Var cachedObjectVar;	/* TODO: When to flush this cache? Can class +				 * variables be cached? */ +} OOResVarInfo; + +/* + * Function declarations for things defined in this file. + */ + +static Tcl_Obj **	InitEnsembleRewrite(Tcl_Interp *interp, int objc, +			    Tcl_Obj *const *objv, int toRewrite, +			    int rewriteLength, Tcl_Obj *const *rewriteObjs, +			    int *lengthPtr); +static int		InvokeProcedureMethod(ClientData clientData, +			    Tcl_Interp *interp, Tcl_ObjectContext context, +			    int objc, Tcl_Obj *const *objv); +static int		FinalizeForwardCall(ClientData data[], Tcl_Interp *interp, +			    int result); +static int		FinalizePMCall(ClientData data[], Tcl_Interp *interp, +			    int result); +static int		PushMethodCallFrame(Tcl_Interp *interp, +			    CallContext *contextPtr, ProcedureMethod *pmPtr, +			    int objc, Tcl_Obj *const *objv, +			    PMFrameData *fdPtr); +static void		DeleteProcedureMethodRecord(ProcedureMethod *pmPtr); +static void		DeleteProcedureMethod(ClientData clientData); +static int		CloneProcedureMethod(Tcl_Interp *interp, +			    ClientData clientData, ClientData *newClientData); +static void		MethodErrorHandler(Tcl_Interp *interp, +			    Tcl_Obj *procNameObj); +static void		ConstructorErrorHandler(Tcl_Interp *interp, +			    Tcl_Obj *procNameObj); +static void		DestructorErrorHandler(Tcl_Interp *interp, +			    Tcl_Obj *procNameObj); +static Tcl_Obj *	RenderDeclarerName(ClientData clientData); +static int		InvokeForwardMethod(ClientData clientData, +			    Tcl_Interp *interp, Tcl_ObjectContext context, +			    int objc, Tcl_Obj *const *objv); +static void		DeleteForwardMethod(ClientData clientData); +static int		CloneForwardMethod(Tcl_Interp *interp, +			    ClientData clientData, ClientData *newClientData); +static int		ProcedureMethodVarResolver(Tcl_Interp *interp, +			    const char *varName, Tcl_Namespace *contextNs, +			    int flags, Tcl_Var *varPtr); +static int		ProcedureMethodCompiledVarResolver(Tcl_Interp *interp, +			    const char *varName, int length, +			    Tcl_Namespace *contextNs, +			    Tcl_ResolvedVarInfo **rPtrPtr); + +/* + * The types of methods defined by the core OO system. + */ + +static const Tcl_MethodType procMethodType = { +    TCL_OO_METHOD_VERSION_CURRENT, "method", +    InvokeProcedureMethod, DeleteProcedureMethod, CloneProcedureMethod +}; +static const Tcl_MethodType fwdMethodType = { +    TCL_OO_METHOD_VERSION_CURRENT, "forward", +    InvokeForwardMethod, DeleteForwardMethod, CloneForwardMethod +}; + +/* + * Helper macros (derived from things private to tclVar.c) + */ + +#define TclVarTable(contextNs) \ +    ((Tcl_HashTable *) (&((Namespace *) (contextNs))->varTable)) +#define TclVarHashGetValue(hPtr) \ +    ((Tcl_Var) ((char *)hPtr - TclOffset(VarInHash, entry))) + +/* + * ---------------------------------------------------------------------- + * + * Tcl_NewInstanceMethod -- + * + *	Attach a method to an object instance. + * + * ---------------------------------------------------------------------- + */ + +Tcl_Method +Tcl_NewInstanceMethod( +    Tcl_Interp *interp,		/* Unused? */ +    Tcl_Object object,		/* The object that has the method attached to +				 * it. */ +    Tcl_Obj *nameObj,		/* The name of the method. May be NULL; if so, +				 * up to caller to manage storage (e.g., when +				 * it is a constructor or destructor). */ +    int flags,			/* Whether this is a public method. */ +    const Tcl_MethodType *typePtr, +				/* The type of method this is, which defines +				 * how to invoke, delete and clone the +				 * method. */ +    ClientData clientData)	/* Some data associated with the particular +				 * method to be created. */ +{ +    register Object *oPtr = (Object *) object; +    register Method *mPtr; +    Tcl_HashEntry *hPtr; +    int isNew; + +    if (nameObj == NULL) { +	mPtr = ckalloc(sizeof(Method)); +	mPtr->namePtr = NULL; +	mPtr->refCount = 1; +	goto populate; +    } +    if (!oPtr->methodsPtr) { +	oPtr->methodsPtr = ckalloc(sizeof(Tcl_HashTable)); +	Tcl_InitObjHashTable(oPtr->methodsPtr); +	oPtr->flags &= ~USE_CLASS_CACHE; +    } +    hPtr = Tcl_CreateHashEntry(oPtr->methodsPtr, (char *) nameObj, &isNew); +    if (isNew) { +	mPtr = ckalloc(sizeof(Method)); +	mPtr->namePtr = nameObj; +	mPtr->refCount = 1; +	Tcl_IncrRefCount(nameObj); +	Tcl_SetHashValue(hPtr, mPtr); +    } else { +	mPtr = Tcl_GetHashValue(hPtr); +	if (mPtr->typePtr != NULL && mPtr->typePtr->deleteProc != NULL) { +	    mPtr->typePtr->deleteProc(mPtr->clientData); +	} +    } + +  populate: +    mPtr->typePtr = typePtr; +    mPtr->clientData = clientData; +    mPtr->flags = 0; +    mPtr->declaringObjectPtr = oPtr; +    mPtr->declaringClassPtr = NULL; +    if (flags) { +	mPtr->flags |= flags & (PUBLIC_METHOD | PRIVATE_METHOD); +    } +    oPtr->epoch++; +    return (Tcl_Method) mPtr; +} + +/* + * ---------------------------------------------------------------------- + * + * Tcl_NewMethod -- + * + *	Attach a method to a class. + * + * ---------------------------------------------------------------------- + */ + +Tcl_Method +Tcl_NewMethod( +    Tcl_Interp *interp,		/* The interpreter containing the class. */ +    Tcl_Class cls,		/* The class to attach the method to. */ +    Tcl_Obj *nameObj,		/* The name of the object. May be NULL (e.g., +				 * for constructors or destructors); if so, up +				 * to caller to manage storage. */ +    int flags,			/* Whether this is a public method. */ +    const Tcl_MethodType *typePtr, +				/* The type of method this is, which defines +				 * how to invoke, delete and clone the +				 * method. */ +    ClientData clientData)	/* Some data associated with the particular +				 * method to be created. */ +{ +    register Class *clsPtr = (Class *) cls; +    register Method *mPtr; +    Tcl_HashEntry *hPtr; +    int isNew; + +    if (nameObj == NULL) { +	mPtr = ckalloc(sizeof(Method)); +	mPtr->namePtr = NULL; +	mPtr->refCount = 1; +	goto populate; +    } +    hPtr = Tcl_CreateHashEntry(&clsPtr->classMethods, (char *)nameObj,&isNew); +    if (isNew) { +	mPtr = ckalloc(sizeof(Method)); +	mPtr->refCount = 1; +	mPtr->namePtr = nameObj; +	Tcl_IncrRefCount(nameObj); +	Tcl_SetHashValue(hPtr, mPtr); +    } else { +	mPtr = Tcl_GetHashValue(hPtr); +	if (mPtr->typePtr != NULL && mPtr->typePtr->deleteProc != NULL) { +	    mPtr->typePtr->deleteProc(mPtr->clientData); +	} +    } + +  populate: +    clsPtr->thisPtr->fPtr->epoch++; +    mPtr->typePtr = typePtr; +    mPtr->clientData = clientData; +    mPtr->flags = 0; +    mPtr->declaringObjectPtr = NULL; +    mPtr->declaringClassPtr = clsPtr; +    if (flags) { +	mPtr->flags |= flags & (PUBLIC_METHOD | PRIVATE_METHOD); +    } + +    return (Tcl_Method) mPtr; +} + +/* + * ---------------------------------------------------------------------- + * + * TclOODelMethodRef -- + * + *	How to delete a method. + * + * ---------------------------------------------------------------------- + */ + +void +TclOODelMethodRef( +    Method *mPtr) +{ +    if ((mPtr != NULL) && (--mPtr->refCount <= 0)) { +	if (mPtr->typePtr != NULL && mPtr->typePtr->deleteProc != NULL) { +	    mPtr->typePtr->deleteProc(mPtr->clientData); +	} +	if (mPtr->namePtr != NULL) { +	    Tcl_DecrRefCount(mPtr->namePtr); +	} + +	ckfree(mPtr); +    } +} + +/* + * ---------------------------------------------------------------------- + * + * TclOONewBasicMethod -- + * + *	Helper that makes it cleaner to create very simple methods during + *	basic system initialization. Not suitable for general use. + * + * ---------------------------------------------------------------------- + */ + +void +TclOONewBasicMethod( +    Tcl_Interp *interp, +    Class *clsPtr,		/* Class to attach the method to. */ +    const DeclaredClassMethod *dcm) +				/* Name of the method, whether it is public, +				 * and the function to implement it. */ +{ +    Tcl_Obj *namePtr = Tcl_NewStringObj(dcm->name, -1); + +    Tcl_IncrRefCount(namePtr); +    Tcl_NewMethod(interp, (Tcl_Class) clsPtr, namePtr, +	    (dcm->isPublic ? PUBLIC_METHOD : 0), &dcm->definition, NULL); +    Tcl_DecrRefCount(namePtr); +} + +/* + * ---------------------------------------------------------------------- + * + * TclOONewProcInstanceMethod -- + * + *	Create a new procedure-like method for an object. + * + * ---------------------------------------------------------------------- + */ + +Method * +TclOONewProcInstanceMethod( +    Tcl_Interp *interp,		/* The interpreter containing the object. */ +    Object *oPtr,		/* The object to modify. */ +    int flags,			/* Whether this is a public method. */ +    Tcl_Obj *nameObj,		/* The name of the method, which must not be +				 * NULL. */ +    Tcl_Obj *argsObj,		/* The formal argument list for the method, +				 * which must not be NULL. */ +    Tcl_Obj *bodyObj,		/* The body of the method, which must not be +				 * NULL. */ +    ProcedureMethod **pmPtrPtr)	/* Place to write pointer to procedure method +				 * structure to allow for deeper tuning of the +				 * structure's contents. NULL if caller is not +				 * interested. */ +{ +    int argsLen; +    register ProcedureMethod *pmPtr; +    Tcl_Method method; + +    if (Tcl_ListObjLength(interp, argsObj, &argsLen) != TCL_OK) { +	return NULL; +    } +    pmPtr = ckalloc(sizeof(ProcedureMethod)); +    memset(pmPtr, 0, sizeof(ProcedureMethod)); +    pmPtr->version = TCLOO_PROCEDURE_METHOD_VERSION; +    pmPtr->flags = flags & USE_DECLARER_NS; +    pmPtr->refCount = 1; + +    method = TclOOMakeProcInstanceMethod(interp, oPtr, flags, nameObj, +	    argsObj, bodyObj, &procMethodType, pmPtr, &pmPtr->procPtr); +    if (method == NULL) { +	ckfree(pmPtr); +    } else if (pmPtrPtr != NULL) { +	*pmPtrPtr = pmPtr; +    } +    return (Method *) method; +} + +/* + * ---------------------------------------------------------------------- + * + * TclOONewProcMethod -- + * + *	Create a new procedure-like method for a class. + * + * ---------------------------------------------------------------------- + */ + +Method * +TclOONewProcMethod( +    Tcl_Interp *interp,		/* The interpreter containing the class. */ +    Class *clsPtr,		/* The class to modify. */ +    int flags,			/* Whether this is a public method. */ +    Tcl_Obj *nameObj,		/* The name of the method, which may be NULL; +				 * if so, up to caller to manage storage +				 * (e.g., because it is a constructor or +				 * destructor). */ +    Tcl_Obj *argsObj,		/* The formal argument list for the method, +				 * which may be NULL; if so, it is equivalent +				 * to an empty list. */ +    Tcl_Obj *bodyObj,		/* The body of the method, which must not be +				 * NULL. */ +    ProcedureMethod **pmPtrPtr)	/* Place to write pointer to procedure method +				 * structure to allow for deeper tuning of the +				 * structure's contents. NULL if caller is not +				 * interested. */ +{ +    int argsLen;		/* -1 => delete argsObj before exit */ +    register ProcedureMethod *pmPtr; +    const char *procName; +    Tcl_Method method; + +    if (argsObj == NULL) { +	argsLen = -1; +	argsObj = Tcl_NewObj(); +	Tcl_IncrRefCount(argsObj); +	procName = "<destructor>"; +    } else if (Tcl_ListObjLength(interp, argsObj, &argsLen) != TCL_OK) { +	return NULL; +    } else { +	procName = (nameObj==NULL ? "<constructor>" : TclGetString(nameObj)); +    } + +    pmPtr = ckalloc(sizeof(ProcedureMethod)); +    memset(pmPtr, 0, sizeof(ProcedureMethod)); +    pmPtr->version = TCLOO_PROCEDURE_METHOD_VERSION; +    pmPtr->flags = flags & USE_DECLARER_NS; +    pmPtr->refCount = 1; + +    method = TclOOMakeProcMethod(interp, clsPtr, flags, nameObj, procName, +	    argsObj, bodyObj, &procMethodType, pmPtr, &pmPtr->procPtr); + +    if (argsLen == -1) { +	Tcl_DecrRefCount(argsObj); +    } +    if (method == NULL) { +	ckfree(pmPtr); +    } else if (pmPtrPtr != NULL) { +	*pmPtrPtr = pmPtr; +    } + +    return (Method *) method; +} + +/* + * ---------------------------------------------------------------------- + * + * TclOOMakeProcInstanceMethod -- + * + *	The guts of the code to make a procedure-like method for an object. + *	Split apart so that it is easier for other extensions to reuse (in + *	particular, it frees them from having to pry so deeply into Tcl's + *	guts). + * + * ---------------------------------------------------------------------- + */ + +Tcl_Method +TclOOMakeProcInstanceMethod( +    Tcl_Interp *interp,		/* The interpreter containing the object. */ +    Object *oPtr,		/* The object to modify. */ +    int flags,			/* Whether this is a public method. */ +    Tcl_Obj *nameObj,		/* The name of the method, which _must not_ be +				 * NULL. */ +    Tcl_Obj *argsObj,		/* The formal argument list for the method, +				 * which _must not_ be NULL. */ +    Tcl_Obj *bodyObj,		/* The body of the method, which _must not_ be +				 * NULL. */ +    const Tcl_MethodType *typePtr, +				/* The type of the method to create. */ +    ClientData clientData,	/* The per-method type-specific data. */ +    Proc **procPtrPtr)		/* A pointer to the variable in which to write +				 * the procedure record reference. Presumably +				 * inside the structure indicated by the +				 * pointer in clientData. */ +{ +    Interp *iPtr = (Interp *) interp; +    Proc *procPtr; + +    if (TclCreateProc(interp, NULL, TclGetString(nameObj), argsObj, bodyObj, +	    procPtrPtr) != TCL_OK) { +	return NULL; +    } +    procPtr = *procPtrPtr; +    procPtr->cmdPtr = NULL; + +    if (iPtr->cmdFramePtr) { +	CmdFrame context = *iPtr->cmdFramePtr; + +	if (context.type == TCL_LOCATION_BC) { +	    /* +	     * Retrieve source information from the bytecode, if possible. If +	     * the information is retrieved successfully, context.type will be +	     * TCL_LOCATION_SOURCE and the reference held by +	     * context.data.eval.path will be counted. +	     */ + +	    TclGetSrcInfoForPc(&context); +	} else if (context.type == TCL_LOCATION_SOURCE) { +	    /* +	     * The copy into 'context' up above has created another reference +	     * to 'context.data.eval.path'; account for it. +	     */ + +	    Tcl_IncrRefCount(context.data.eval.path); +	} + +	if (context.type == TCL_LOCATION_SOURCE) { +	    /* +	     * We can account for source location within a proc only if the +	     * proc body was not created by substitution. +	     * (FIXME: check that this is sane and correct!) +	     */ + +	    if (context.line +		    && (context.nline >= 4) && (context.line[3] >= 0)) { +		int isNew; +		CmdFrame *cfPtr = ckalloc(sizeof(CmdFrame)); +		Tcl_HashEntry *hPtr; + +		cfPtr->level = -1; +		cfPtr->type = context.type; +		cfPtr->line = ckalloc(sizeof(int)); +		cfPtr->line[0] = context.line[3]; +		cfPtr->nline = 1; +		cfPtr->framePtr = NULL; +		cfPtr->nextPtr = NULL; + +		cfPtr->data.eval.path = context.data.eval.path; +		Tcl_IncrRefCount(cfPtr->data.eval.path); + +		cfPtr->cmd = NULL; +		cfPtr->len = 0; + +		hPtr = Tcl_CreateHashEntry(iPtr->linePBodyPtr, +			(char *) procPtr, &isNew); +		Tcl_SetHashValue(hPtr, cfPtr); +	    } + +	    /* +	     * 'context' is going out of scope; account for the reference that +	     * it's holding to the path name. +	     */ + +	    Tcl_DecrRefCount(context.data.eval.path); +	    context.data.eval.path = NULL; +	} +    } + +    return Tcl_NewInstanceMethod(interp, (Tcl_Object) oPtr, nameObj, flags, +	    typePtr, clientData); +} + +/* + * ---------------------------------------------------------------------- + * + * TclOOMakeProcMethod -- + * + *	The guts of the code to make a procedure-like method for a class. + *	Split apart so that it is easier for other extensions to reuse (in + *	particular, it frees them from having to pry so deeply into Tcl's + *	guts). + * + * ---------------------------------------------------------------------- + */ + +Tcl_Method +TclOOMakeProcMethod( +    Tcl_Interp *interp,		/* The interpreter containing the class. */ +    Class *clsPtr,		/* The class to modify. */ +    int flags,			/* Whether this is a public method. */ +    Tcl_Obj *nameObj,		/* The name of the method, which may be NULL; +				 * if so, up to caller to manage storage +				 * (e.g., because it is a constructor or +				 * destructor). */ +    const char *namePtr,	/* The name of the method as a string, which +				 * _must not_ be NULL. */ +    Tcl_Obj *argsObj,		/* The formal argument list for the method, +				 * which _must not_ be NULL. */ +    Tcl_Obj *bodyObj,		/* The body of the method, which _must not_ be +				 * NULL. */ +    const Tcl_MethodType *typePtr, +				/* The type of the method to create. */ +    ClientData clientData,	/* The per-method type-specific data. */ +    Proc **procPtrPtr)		/* A pointer to the variable in which to write +				 * the procedure record reference. Presumably +				 * inside the structure indicated by the +				 * pointer in clientData. */ +{ +    Interp *iPtr = (Interp *) interp; +    Proc *procPtr; + +    if (TclCreateProc(interp, NULL, namePtr, argsObj, bodyObj, +	    procPtrPtr) != TCL_OK) { +	return NULL; +    } +    procPtr = *procPtrPtr; +    procPtr->cmdPtr = NULL; + +    if (iPtr->cmdFramePtr) { +	CmdFrame context = *iPtr->cmdFramePtr; + +	if (context.type == TCL_LOCATION_BC) { +	    /* +	     * Retrieve source information from the bytecode, if possible. If +	     * the information is retrieved successfully, context.type will be +	     * TCL_LOCATION_SOURCE and the reference held by +	     * context.data.eval.path will be counted. +	     */ + +	    TclGetSrcInfoForPc(&context); +	} else if (context.type == TCL_LOCATION_SOURCE) { +	    /* +	     * The copy into 'context' up above has created another reference +	     * to 'context.data.eval.path'; account for it. +	     */ + +	    Tcl_IncrRefCount(context.data.eval.path); +	} + +	if (context.type == TCL_LOCATION_SOURCE) { +	    /* +	     * We can account for source location within a proc only if the +	     * proc body was not created by substitution. +	     * (FIXME: check that this is sane and correct!) +	     */ + +	    if (context.line +		    && (context.nline >= 4) && (context.line[3] >= 0)) { +		int isNew; +		CmdFrame *cfPtr = ckalloc(sizeof(CmdFrame)); +		Tcl_HashEntry *hPtr; + +		cfPtr->level = -1; +		cfPtr->type = context.type; +		cfPtr->line = ckalloc(sizeof(int)); +		cfPtr->line[0] = context.line[3]; +		cfPtr->nline = 1; +		cfPtr->framePtr = NULL; +		cfPtr->nextPtr = NULL; + +		cfPtr->data.eval.path = context.data.eval.path; +		Tcl_IncrRefCount(cfPtr->data.eval.path); + +		cfPtr->cmd = NULL; +		cfPtr->len = 0; + +		hPtr = Tcl_CreateHashEntry(iPtr->linePBodyPtr, +			(char *) procPtr, &isNew); +		Tcl_SetHashValue(hPtr, cfPtr); +	    } + +	    /* +	     * 'context' is going out of scope; account for the reference that +	     * it's holding to the path name. +	     */ + +	    Tcl_DecrRefCount(context.data.eval.path); +	    context.data.eval.path = NULL; +	} +    } + +    return Tcl_NewMethod(interp, (Tcl_Class) clsPtr, nameObj, flags, typePtr, +	    clientData); +} + +/* + * ---------------------------------------------------------------------- + * + * InvokeProcedureMethod, PushMethodCallFrame -- + * + *	How to invoke a procedure-like method. + * + * ---------------------------------------------------------------------- + */ + +static int +InvokeProcedureMethod( +    ClientData clientData,	/* Pointer to some per-method context. */ +    Tcl_Interp *interp, +    Tcl_ObjectContext context,	/* The method calling context. */ +    int objc,			/* Number of arguments. */ +    Tcl_Obj *const *objv)	/* Arguments as actually seen. */ +{ +    ProcedureMethod *pmPtr = clientData; +    int result; +    PMFrameData *fdPtr;		/* Important data that has to have a lifetime +				 * matched by this function (or rather, by the +				 * call frame's lifetime). */ + +    /* +     * If the interpreter was deleted, we just skip to the next thing in the +     * chain. +     */ + +    if (Tcl_InterpDeleted(interp)) { +	return TclNRObjectContextInvokeNext(interp, context, objc, objv, +		Tcl_ObjectContextSkippedArgs(context)); +    } + +    /* +     * Allocate the special frame data. +     */ + +    fdPtr = TclStackAlloc(interp, sizeof(PMFrameData)); + +    /* +     * Create a call frame for this method. +     */ + +    result = PushMethodCallFrame(interp, (CallContext *) context, pmPtr, +	    objc, objv, fdPtr); +    if (result != TCL_OK) { +	TclStackFree(interp, fdPtr); +	return result; +    } +    pmPtr->refCount++; + +    /* +     * Give the pre-call callback a chance to do some setup and, possibly, +     * veto the call. +     */ + +    if (pmPtr->preCallProc != NULL) { +	int isFinished; + +	result = pmPtr->preCallProc(pmPtr->clientData, interp, context, +		(Tcl_CallFrame *) fdPtr->framePtr, &isFinished); +	if (isFinished || result != TCL_OK) { +	    /* +	     * Restore the old cmdPtr so that a subsequent use of [info frame] +	     * won't crash on us. [Bug 3001438] +	     */ + +	    pmPtr->procPtr->cmdPtr = fdPtr->oldCmdPtr; + +	    Tcl_PopCallFrame(interp); +	    TclStackFree(interp, fdPtr->framePtr); +	    if (--pmPtr->refCount < 1) { +		DeleteProcedureMethodRecord(pmPtr); +	    } +	    TclStackFree(interp, fdPtr); +	    return result; +	} +    } + +    /* +     * Now invoke the body of the method. +     */ + +    TclNRAddCallback(interp, FinalizePMCall, pmPtr, context, fdPtr, NULL); +    return TclNRInterpProcCore(interp, fdPtr->nameObj, +	    Tcl_ObjectContextSkippedArgs(context), fdPtr->errProc); +} + +static int +FinalizePMCall( +    ClientData data[], +    Tcl_Interp *interp, +    int result) +{ +    ProcedureMethod *pmPtr = data[0]; +    Tcl_ObjectContext context = data[1]; +    PMFrameData *fdPtr = data[2]; + +    /* +     * Give the post-call callback a chance to do some cleanup. Note that at +     * this point the call frame itself is invalid; it's already been popped. +     */ + +    if (pmPtr->postCallProc) { +	result = pmPtr->postCallProc(pmPtr->clientData, interp, context, +		Tcl_GetObjectNamespace(Tcl_ObjectContextObject(context)), +		result); +    } + +    /* +     * Restore the old cmdPtr so that a subsequent use of [info frame] won't +     * crash on us. [Bug 3001438] +     */ + +    pmPtr->procPtr->cmdPtr = fdPtr->oldCmdPtr; + +    /* +     * Scrap the special frame data now that we're done with it. Note that we +     * are inlining DeleteProcedureMethod() here; this location is highly +     * sensitive when it comes to performance! +     */ + +    if (--pmPtr->refCount < 1) { +	DeleteProcedureMethodRecord(pmPtr); +    } +    TclStackFree(interp, fdPtr); +    return result; +} + +static int +PushMethodCallFrame( +    Tcl_Interp *interp,		/* Current interpreter. */ +    CallContext *contextPtr,	/* Current method call context. */ +    ProcedureMethod *pmPtr,	/* Information about this procedure-like +				 * method. */ +    int objc,			/* Number of arguments. */ +    Tcl_Obj *const *objv,	/* Array of arguments. */ +    PMFrameData *fdPtr)		/* Place to store information about the call +				 * frame. */ +{ +    Namespace *nsPtr = (Namespace *) contextPtr->oPtr->namespacePtr; +    register int result; +    const char *namePtr; +    CallFrame **framePtrPtr = &fdPtr->framePtr; + +    /* +     * Compute basic information on the basis of the type of method it is. +     */ + +    if (contextPtr->callPtr->flags & CONSTRUCTOR) { +	namePtr = "<constructor>"; +	fdPtr->nameObj = contextPtr->oPtr->fPtr->constructorName; +	fdPtr->errProc = ConstructorErrorHandler; +    } else if (contextPtr->callPtr->flags & DESTRUCTOR) { +	namePtr = "<destructor>"; +	fdPtr->nameObj = contextPtr->oPtr->fPtr->destructorName; +	fdPtr->errProc = DestructorErrorHandler; +    } else { +	fdPtr->nameObj = Tcl_MethodName( +		Tcl_ObjectContextMethod((Tcl_ObjectContext) contextPtr)); +	namePtr = TclGetString(fdPtr->nameObj); +	fdPtr->errProc = MethodErrorHandler; +    } +    if (pmPtr->errProc != NULL) { +	fdPtr->errProc = pmPtr->errProc; +    } + +    /* +     * Magic to enable things like [incr Tcl], which wants methods to run in +     * their class's namespace. +     */ + +    if (pmPtr->flags & USE_DECLARER_NS) { +	register Method *mPtr = +		contextPtr->callPtr->chain[contextPtr->index].mPtr; + +	if (mPtr->declaringClassPtr != NULL) { +	    nsPtr = (Namespace *) +		    mPtr->declaringClassPtr->thisPtr->namespacePtr; +	} else { +	    nsPtr = (Namespace *) mPtr->declaringObjectPtr->namespacePtr; +	} +    } + +    /* +     * Save the old cmdPtr so that when this recursive call returns, we can +     * restore it. To do otherwise causes crashes in [info frame] after we +     * return from a recursive call. [Bug 3001438] +     */ + +    fdPtr->oldCmdPtr = pmPtr->procPtr->cmdPtr; + +    /* +     * Compile the body. This operation may fail. +     */ + +    fdPtr->efi.length = 2; +    memset(&fdPtr->cmd, 0, sizeof(Command)); +    fdPtr->cmd.nsPtr = nsPtr; +    fdPtr->cmd.clientData = &fdPtr->efi; +    pmPtr->procPtr->cmdPtr = &fdPtr->cmd; + +    /* +     * [Bug 2037727] Always call TclProcCompileProc so that we check not only +     * that we have bytecode, but also that it remains valid. Note that we set +     * the namespace of the code here directly; this is a hack, but the +     * alternative is *so* slow... +     */ + +    if (pmPtr->procPtr->bodyPtr->typePtr == &tclByteCodeType) { +	ByteCode *codePtr = +		pmPtr->procPtr->bodyPtr->internalRep.twoPtrValue.ptr1; + +	codePtr->nsPtr = nsPtr; +    } +    result = TclProcCompileProc(interp, pmPtr->procPtr, +	    pmPtr->procPtr->bodyPtr, nsPtr, "body of method", namePtr); +    if (result != TCL_OK) { +	goto failureReturn; +    } + +    /* +     * Make the stack frame and fill it out with information about this call. +     * This operation may fail. +     */ + +    result = TclPushStackFrame(interp, (Tcl_CallFrame **) framePtrPtr, +	    (Tcl_Namespace *) nsPtr, FRAME_IS_PROC|FRAME_IS_METHOD); +    if (result != TCL_OK) { +	goto failureReturn; +    } + +    fdPtr->framePtr->clientData = contextPtr; +    fdPtr->framePtr->objc = objc; +    fdPtr->framePtr->objv = objv; +    fdPtr->framePtr->procPtr = pmPtr->procPtr; + +    /* +     * Finish filling out the extra frame info so that [info frame] works. +     */ + +    fdPtr->efi.fields[0].name = "method"; +    fdPtr->efi.fields[0].proc = NULL; +    fdPtr->efi.fields[0].clientData = fdPtr->nameObj; +    if (pmPtr->gfivProc != NULL) { +	fdPtr->efi.fields[1].name = ""; +	fdPtr->efi.fields[1].proc = pmPtr->gfivProc; +	fdPtr->efi.fields[1].clientData = pmPtr; +    } else { +	register Tcl_Method method = +		Tcl_ObjectContextMethod((Tcl_ObjectContext) contextPtr); + +	if (Tcl_MethodDeclarerObject(method) != NULL) { +	    fdPtr->efi.fields[1].name = "object"; +	} else { +	    fdPtr->efi.fields[1].name = "class"; +	} +	fdPtr->efi.fields[1].proc = RenderDeclarerName; +	fdPtr->efi.fields[1].clientData = &fdPtr->pni; +	fdPtr->pni.interp = interp; +	fdPtr->pni.method = method; +    } + +    return TCL_OK; + +    /* +     * Restore the old cmdPtr so that a subsequent use of [info frame] won't +     * crash on us. [Bug 3001438] +     */ + +  failureReturn: +    pmPtr->procPtr->cmdPtr = fdPtr->oldCmdPtr; +    return result; +} + +/* + * ---------------------------------------------------------------------- + * + * TclOOSetupVariableResolver, etc. -- + * + *	Variable resolution engine used to connect declared variables to local + *	variables used in methods. The compiled variable resolver is more + *	important, but both are needed as it is possible to have a variable + *	that is only referred to in ways that aren't compilable and we can't + *	force LVT presence. [TIP #320] + * + * ---------------------------------------------------------------------- + */ + +void +TclOOSetupVariableResolver( +    Tcl_Namespace *nsPtr) +{ +    Tcl_ResolverInfo info; + +    Tcl_GetNamespaceResolvers(nsPtr, &info); +    if (info.compiledVarResProc == NULL) { +	Tcl_SetNamespaceResolvers(nsPtr, NULL, ProcedureMethodVarResolver, +		ProcedureMethodCompiledVarResolver); +    } +} + +static int +ProcedureMethodVarResolver( +    Tcl_Interp *interp, +    const char *varName, +    Tcl_Namespace *contextNs, +    int flags, +    Tcl_Var *varPtr) +{ +    int result; +    Tcl_ResolvedVarInfo *rPtr = NULL; +     +    result = ProcedureMethodCompiledVarResolver(interp, varName, +	    strlen(varName), contextNs, &rPtr); + +    if (result != TCL_OK) { +	return result; +    } + +    *varPtr = rPtr->fetchProc(interp, rPtr); + +    /* +     * Must not retain reference to resolved information. [Bug 3105999] +     */ + +    if (rPtr != NULL) { +	rPtr->deleteProc(rPtr); +    } +    return (*varPtr? TCL_OK : TCL_CONTINUE); +} + +static Tcl_Var +ProcedureMethodCompiledVarConnect( +    Tcl_Interp *interp, +    Tcl_ResolvedVarInfo *rPtr) +{ +    OOResVarInfo *infoPtr = (OOResVarInfo *) rPtr; +    Interp *iPtr = (Interp *) interp; +    CallFrame *framePtr = iPtr->varFramePtr; +    CallContext *contextPtr; +    Tcl_Obj *variableObj; +    Tcl_HashEntry *hPtr; +    int i, isNew, cacheIt, varLen, len; +    const char *match, *varName; + +    /* +     * Check that the variable is being requested in a context that is also a +     * method call; if not (i.e. we're evaluating in the object's namespace or +     * in a procedure of that namespace) then we do nothing. +     */ + +    if (framePtr == NULL || !(framePtr->isProcCallFrame & FRAME_IS_METHOD)) { +	return NULL; +    } +    contextPtr = framePtr->clientData; + +    /* +     * If we've done the work before (in a comparable context) then reuse that +     * rather than performing resolution ourselves. +     */ + +    if (infoPtr->cachedObjectVar) { +	return infoPtr->cachedObjectVar; +    } + +    /* +     * Check if the variable is one we want to resolve at all (i.e. whether it +     * is in the list provided by the user). If not, we mustn't do anything +     * either. +     */ + +    varName = TclGetStringFromObj(infoPtr->variableObj, &varLen); +    if (contextPtr->callPtr->chain[contextPtr->index] +	    .mPtr->declaringClassPtr != NULL) { +	FOREACH(variableObj, contextPtr->callPtr->chain[contextPtr->index] +		.mPtr->declaringClassPtr->variables) { +	    match = TclGetStringFromObj(variableObj, &len); +	    if ((len == varLen) && !memcmp(match, varName, len)) { +		cacheIt = 0; +		goto gotMatch; +	    } +	} +    } else { +	FOREACH(variableObj, contextPtr->oPtr->variables) { +	    match = TclGetStringFromObj(variableObj, &len); +	    if ((len == varLen) && !memcmp(match, varName, len)) { +		cacheIt = 1; +		goto gotMatch; +	    } +	} +    } +    return NULL; + +    /* +     * It is a variable we want to resolve, so resolve it. +     */ + +  gotMatch: +    hPtr = Tcl_CreateHashEntry(TclVarTable(contextPtr->oPtr->namespacePtr), +	    (char *) variableObj, &isNew); +    if (isNew) { +	TclSetVarNamespaceVar((Var *) TclVarHashGetValue(hPtr)); +    } +    if (cacheIt) { +	infoPtr->cachedObjectVar = TclVarHashGetValue(hPtr); + +	/* +	 * We must keep a reference to the variable so everything will +	 * continue to work correctly even if it is unset; being unset does +	 * not end the life of the variable at this level. [Bug 3185009] +	 */ + +	VarHashRefCount(infoPtr->cachedObjectVar)++; +    } +    return TclVarHashGetValue(hPtr); +} + +static void +ProcedureMethodCompiledVarDelete( +    Tcl_ResolvedVarInfo *rPtr) +{ +    OOResVarInfo *infoPtr = (OOResVarInfo *) rPtr; + +    /* +     * Release the reference to the variable if we were holding it. +     */ + +    if (infoPtr->cachedObjectVar) { +	VarHashRefCount(infoPtr->cachedObjectVar)--; +	TclCleanupVar((Var *) infoPtr->cachedObjectVar, NULL); +    } +    Tcl_DecrRefCount(infoPtr->variableObj); +    ckfree(infoPtr); +} + +static int +ProcedureMethodCompiledVarResolver( +    Tcl_Interp *interp, +    const char *varName, +    int length, +    Tcl_Namespace *contextNs, +    Tcl_ResolvedVarInfo **rPtrPtr) +{ +    OOResVarInfo *infoPtr; +    Tcl_Obj *variableObj = Tcl_NewStringObj(varName, length); + +    /* +     * Do not create resolvers for cases that contain namespace separators or +     * which look like array accesses. Both will lead us astray. +     */ + +    if (strstr(Tcl_GetString(variableObj), "::") != NULL || +	    Tcl_StringMatch(Tcl_GetString(variableObj), "*(*)")) { +	Tcl_DecrRefCount(variableObj); +	return TCL_CONTINUE; +    } + +    infoPtr = ckalloc(sizeof(OOResVarInfo)); +    infoPtr->info.fetchProc = ProcedureMethodCompiledVarConnect; +    infoPtr->info.deleteProc = ProcedureMethodCompiledVarDelete; +    infoPtr->cachedObjectVar = NULL; +    infoPtr->variableObj = variableObj; +    Tcl_IncrRefCount(variableObj); +    *rPtrPtr = &infoPtr->info; +    return TCL_OK; +} + +/* + * ---------------------------------------------------------------------- + * + * RenderDeclarerName -- + * + *	Returns the name of the entity (object or class) which declared a + *	method. Used for producing information for [info frame] in such a way + *	that the expensive part of this (generating the object or class name + *	itself) isn't done until it is needed. + * + * ---------------------------------------------------------------------- + */ + +static Tcl_Obj * +RenderDeclarerName( +    ClientData clientData) +{ +    struct PNI *pni = clientData; +    Tcl_Object object = Tcl_MethodDeclarerObject(pni->method); + +    if (object == NULL) { +	object = Tcl_GetClassAsObject(Tcl_MethodDeclarerClass(pni->method)); +    } +    return TclOOObjectName(pni->interp, (Object *) object); +} + +/* + * ---------------------------------------------------------------------- + * + * MethodErrorHandler, ConstructorErrorHandler, DestructorErrorHandler -- + * + *	How to fill in the stack trace correctly upon error in various forms + *	of procedure-like methods. LIMIT is how long the inserted strings in + *	the error traces should get before being converted to have ellipses, + *	and ELLIPSIFY is a macro to do the conversion (with the help of a + *	%.*s%s format field). Note that ELLIPSIFY is only safe for use in + *	suitable formatting contexts. + * + * ---------------------------------------------------------------------- + */ + +#define LIMIT 60 +#define ELLIPSIFY(str,len) \ +	((len) > LIMIT ? LIMIT : (len)), (str), ((len) > LIMIT ? "..." : "") + +static void +MethodErrorHandler( +    Tcl_Interp *interp, +    Tcl_Obj *methodNameObj) +{ +    int nameLen, objectNameLen; +    CallContext *contextPtr = ((Interp *) interp)->varFramePtr->clientData; +    Method *mPtr = contextPtr->callPtr->chain[contextPtr->index].mPtr; +    const char *objectName, *kindName, *methodName = +	    Tcl_GetStringFromObj(mPtr->namePtr, &nameLen); +    Object *declarerPtr; + +    if (mPtr->declaringObjectPtr != NULL) { +	declarerPtr = mPtr->declaringObjectPtr; +	kindName = "object"; +    } else { +	if (mPtr->declaringClassPtr == NULL) { +	    Tcl_Panic("method not declared in class or object"); +	} +	declarerPtr = mPtr->declaringClassPtr->thisPtr; +	kindName = "class"; +    } + +    objectName = Tcl_GetStringFromObj(TclOOObjectName(interp, declarerPtr), +	    &objectNameLen); +    Tcl_AppendObjToErrorInfo(interp, Tcl_ObjPrintf( +	    "\n    (%s \"%.*s%s\" method \"%.*s%s\" line %d)", +	    kindName, ELLIPSIFY(objectName, objectNameLen), +	    ELLIPSIFY(methodName, nameLen), Tcl_GetErrorLine(interp))); +} + +static void +ConstructorErrorHandler( +    Tcl_Interp *interp, +    Tcl_Obj *methodNameObj) +{ +    CallContext *contextPtr = ((Interp *) interp)->varFramePtr->clientData; +    Method *mPtr = contextPtr->callPtr->chain[contextPtr->index].mPtr; +    Object *declarerPtr; +    const char *objectName, *kindName; +    int objectNameLen; + +    if (mPtr->declaringObjectPtr != NULL) { +	declarerPtr = mPtr->declaringObjectPtr; +	kindName = "object"; +    } else { +	if (mPtr->declaringClassPtr == NULL) { +	    Tcl_Panic("method not declared in class or object"); +	} +	declarerPtr = mPtr->declaringClassPtr->thisPtr; +	kindName = "class"; +    } + +    objectName = Tcl_GetStringFromObj(TclOOObjectName(interp, declarerPtr), +	    &objectNameLen); +    Tcl_AppendObjToErrorInfo(interp, Tcl_ObjPrintf( +	    "\n    (%s \"%.*s%s\" constructor line %d)", kindName, +	    ELLIPSIFY(objectName, objectNameLen), Tcl_GetErrorLine(interp))); +} + +static void +DestructorErrorHandler( +    Tcl_Interp *interp, +    Tcl_Obj *methodNameObj) +{ +    CallContext *contextPtr = ((Interp *) interp)->varFramePtr->clientData; +    Method *mPtr = contextPtr->callPtr->chain[contextPtr->index].mPtr; +    Object *declarerPtr; +    const char *objectName, *kindName; +    int objectNameLen; + +    if (mPtr->declaringObjectPtr != NULL) { +	declarerPtr = mPtr->declaringObjectPtr; +	kindName = "object"; +    } else { +	if (mPtr->declaringClassPtr == NULL) { +	    Tcl_Panic("method not declared in class or object"); +	} +	declarerPtr = mPtr->declaringClassPtr->thisPtr; +	kindName = "class"; +    } + +    objectName = Tcl_GetStringFromObj(TclOOObjectName(interp, declarerPtr), +	    &objectNameLen); +    Tcl_AppendObjToErrorInfo(interp, Tcl_ObjPrintf( +	    "\n    (%s \"%.*s%s\" destructor line %d)", kindName, +	    ELLIPSIFY(objectName, objectNameLen), Tcl_GetErrorLine(interp))); +} + +/* + * ---------------------------------------------------------------------- + * + * DeleteProcedureMethod, CloneProcedureMethod -- + * + *	How to delete and clone procedure-like methods. + * + * ---------------------------------------------------------------------- + */ + +static void +DeleteProcedureMethodRecord( +    ProcedureMethod *pmPtr) +{ +    TclProcDeleteProc(pmPtr->procPtr); +    if (pmPtr->deleteClientdataProc) { +	pmPtr->deleteClientdataProc(pmPtr->clientData); +    } +    ckfree(pmPtr); +} + +static void +DeleteProcedureMethod( +    ClientData clientData) +{ +    register ProcedureMethod *pmPtr = clientData; + +    if (--pmPtr->refCount < 1) { +	DeleteProcedureMethodRecord(pmPtr); +    } +} + +static int +CloneProcedureMethod( +    Tcl_Interp *interp, +    ClientData clientData, +    ClientData *newClientData) +{ +    ProcedureMethod *pmPtr = clientData; +    ProcedureMethod *pm2Ptr; +    Tcl_Obj *bodyObj, *argsObj; +    CompiledLocal *localPtr; + +    /* +     * Copy the argument list. +     */ + +    argsObj = Tcl_NewObj(); +    for (localPtr=pmPtr->procPtr->firstLocalPtr; localPtr!=NULL; +	    localPtr=localPtr->nextPtr) { +	if (TclIsVarArgument(localPtr)) { +	    Tcl_Obj *argObj = Tcl_NewObj(); + +	    Tcl_ListObjAppendElement(NULL, argObj, +		    Tcl_NewStringObj(localPtr->name, -1)); +	    if (localPtr->defValuePtr != NULL) { +		Tcl_ListObjAppendElement(NULL, argObj, localPtr->defValuePtr); +	    } +	    Tcl_ListObjAppendElement(NULL, argsObj, argObj); +	} +    } + +    /* +     * Must strip the internal representation in order to ensure that any +     * bound references to instance variables are removed. [Bug 3609693] +     */ + +    bodyObj = Tcl_DuplicateObj(pmPtr->procPtr->bodyPtr); +    TclFreeIntRep(bodyObj); + +    /* +     * Create the actual copy of the method record, manufacturing a new proc +     * record. +     */ + +    pm2Ptr = ckalloc(sizeof(ProcedureMethod)); +    memcpy(pm2Ptr, pmPtr, sizeof(ProcedureMethod)); +    pm2Ptr->refCount = 1; +    Tcl_IncrRefCount(argsObj); +    Tcl_IncrRefCount(bodyObj); +    if (TclCreateProc(interp, NULL, "", argsObj, bodyObj, +	    &pm2Ptr->procPtr) != TCL_OK) { +	Tcl_DecrRefCount(argsObj); +	Tcl_DecrRefCount(bodyObj); +	ckfree(pm2Ptr); +	return TCL_ERROR; +    } +    Tcl_DecrRefCount(argsObj); +    Tcl_DecrRefCount(bodyObj); + +    if (pmPtr->cloneClientdataProc) { +	pm2Ptr->clientData = pmPtr->cloneClientdataProc(pmPtr->clientData); +    } +    *newClientData = pm2Ptr; +    return TCL_OK; +} + +/* + * ---------------------------------------------------------------------- + * + * TclOONewForwardMethod -- + * + *	Create a forwarded method for an object. + * + * ---------------------------------------------------------------------- + */ + +Method * +TclOONewForwardInstanceMethod( +    Tcl_Interp *interp,		/* Interpreter for error reporting. */ +    Object *oPtr,		/* The object to attach the method to. */ +    int flags,			/* Whether the method is public or not. */ +    Tcl_Obj *nameObj,		/* The name of the method. */ +    Tcl_Obj *prefixObj)		/* List of arguments that form the command +				 * prefix to forward to. */ +{ +    int prefixLen; +    register ForwardMethod *fmPtr; +    Tcl_Obj *cmdObj; + +    if (Tcl_ListObjLength(interp, prefixObj, &prefixLen) != TCL_OK) { +	return NULL; +    } +    if (prefixLen < 1) { +	Tcl_SetObjResult(interp, Tcl_NewStringObj( +		"method forward prefix must be non-empty", -1)); +	Tcl_SetErrorCode(interp, "TCL", "OO", "BAD_FORWARD", NULL); +	return NULL; +    } + +    fmPtr = ckalloc(sizeof(ForwardMethod)); +    fmPtr->prefixObj = prefixObj; +    Tcl_ListObjIndex(interp, prefixObj, 0, &cmdObj); +    Tcl_IncrRefCount(prefixObj); +    return (Method *) Tcl_NewInstanceMethod(interp, (Tcl_Object) oPtr, +	    nameObj, flags, &fwdMethodType, fmPtr); +} + +/* + * ---------------------------------------------------------------------- + * + * TclOONewForwardMethod -- + * + *	Create a new forwarded method for a class. + * + * ---------------------------------------------------------------------- + */ + +Method * +TclOONewForwardMethod( +    Tcl_Interp *interp,		/* Interpreter for error reporting. */ +    Class *clsPtr,		/* The class to attach the method to. */ +    int flags,			/* Whether the method is public or not. */ +    Tcl_Obj *nameObj,		/* The name of the method. */ +    Tcl_Obj *prefixObj)		/* List of arguments that form the command +				 * prefix to forward to. */ +{ +    int prefixLen; +    register ForwardMethod *fmPtr; +    Tcl_Obj *cmdObj; + +    if (Tcl_ListObjLength(interp, prefixObj, &prefixLen) != TCL_OK) { +	return NULL; +    } +    if (prefixLen < 1) { +	Tcl_SetObjResult(interp, Tcl_NewStringObj( +		"method forward prefix must be non-empty", -1)); +	Tcl_SetErrorCode(interp, "TCL", "OO", "BAD_FORWARD", NULL); +	return NULL; +    } + +    fmPtr = ckalloc(sizeof(ForwardMethod)); +    fmPtr->prefixObj = prefixObj; +    Tcl_ListObjIndex(interp, prefixObj, 0, &cmdObj); +    Tcl_IncrRefCount(prefixObj); +    return (Method *) Tcl_NewMethod(interp, (Tcl_Class) clsPtr, nameObj, +	    flags, &fwdMethodType, fmPtr); +} + +/* + * ---------------------------------------------------------------------- + * + * InvokeForwardMethod -- + * + *	How to invoke a forwarded method. Works by doing some ensemble-like + *	command rearranging and then invokes some other Tcl command. + * + * ---------------------------------------------------------------------- + */ + +static int +InvokeForwardMethod( +    ClientData clientData,	/* Pointer to some per-method context. */ +    Tcl_Interp *interp, +    Tcl_ObjectContext context,	/* The method calling context. */ +    int objc,			/* Number of arguments. */ +    Tcl_Obj *const *objv)	/* Arguments as actually seen. */ +{ +    CallContext *contextPtr = (CallContext *) context; +    ForwardMethod *fmPtr = clientData; +    Tcl_Obj **argObjs, **prefixObjs; +    int numPrefixes, len, skip = contextPtr->skip; + +    /* +     * Build the real list of arguments to use. Note that we know that the +     * prefixObj field of the ForwardMethod structure holds a reference to a +     * non-empty list, so there's a whole class of failures ("not a list") we +     * can ignore here. +     */ + +    Tcl_ListObjGetElements(NULL, fmPtr->prefixObj, &numPrefixes, &prefixObjs); +    argObjs = InitEnsembleRewrite(interp, objc, objv, skip, +	    numPrefixes, prefixObjs, &len); +    Tcl_NRAddCallback(interp, FinalizeForwardCall, argObjs, NULL, NULL, NULL); +    ((Interp *)interp)->lookupNsPtr +	    = (Namespace *) contextPtr->oPtr->namespacePtr; +    return TclNREvalObjv(interp, len, argObjs, TCL_EVAL_NOERR, NULL); +} + +static int +FinalizeForwardCall( +    ClientData data[], +    Tcl_Interp *interp, +    int result) +{ +    Tcl_Obj **argObjs = data[0]; +     +    TclStackFree(interp, argObjs); +    return result; +} + +/* + * ---------------------------------------------------------------------- + * + * DeleteForwardMethod, CloneForwardMethod -- + * + *	How to delete and clone forwarded methods. + * + * ---------------------------------------------------------------------- + */ + +static void +DeleteForwardMethod( +    ClientData clientData) +{ +    ForwardMethod *fmPtr = clientData; + +    Tcl_DecrRefCount(fmPtr->prefixObj); +    ckfree(fmPtr); +} + +static int +CloneForwardMethod( +    Tcl_Interp *interp, +    ClientData clientData, +    ClientData *newClientData) +{ +    ForwardMethod *fmPtr = clientData; +    ForwardMethod *fm2Ptr = ckalloc(sizeof(ForwardMethod)); + +    fm2Ptr->prefixObj = fmPtr->prefixObj; +    Tcl_IncrRefCount(fm2Ptr->prefixObj); +    *newClientData = fm2Ptr; +    return TCL_OK; +} + +/* + * ---------------------------------------------------------------------- + * + * TclOOGetProcFromMethod, TclOOGetFwdFromMethod -- + * + *	Utility functions used for procedure-like and forwarding method + *	introspection. + * + * ---------------------------------------------------------------------- + */ + +Proc * +TclOOGetProcFromMethod( +    Method *mPtr) +{ +    if (mPtr->typePtr == &procMethodType) { +	ProcedureMethod *pmPtr = mPtr->clientData; + +	return pmPtr->procPtr; +    } +    return NULL; +} + +Tcl_Obj * +TclOOGetMethodBody( +    Method *mPtr) +{ +    if (mPtr->typePtr == &procMethodType) { +	ProcedureMethod *pmPtr = mPtr->clientData; + +	if (pmPtr->procPtr->bodyPtr->bytes == NULL) { +	    (void) Tcl_GetString(pmPtr->procPtr->bodyPtr); +	} +	return pmPtr->procPtr->bodyPtr; +    } +    return NULL; +} + +Tcl_Obj * +TclOOGetFwdFromMethod( +    Method *mPtr) +{ +    if (mPtr->typePtr == &fwdMethodType) { +	ForwardMethod *fwPtr = mPtr->clientData; + +	return fwPtr->prefixObj; +    } +    return NULL; +} + +/* + * ---------------------------------------------------------------------- + * + * InitEnsembleRewrite -- + * + *	Utility function that wraps up a lot of the complexity involved in + *	doing ensemble-like command forwarding. Here is a picture of memory + *	management plan: + * + *                    <-----------------objc----------------------> + *      objv:        |=============|===============================| + *                    <-toRewrite->           | + *                                             \ + *                    <-rewriteLength->         \ + *      rewriteObjs: |=================|         \ + *                           |                    | + *                           V                    V + *      argObjs:     |=================|===============================| + *                    <------------------*lengthPtr-------------------> + * + * ---------------------------------------------------------------------- + */ + +static Tcl_Obj ** +InitEnsembleRewrite( +    Tcl_Interp *interp,		/* Place to log the rewrite info. */ +    int objc,			/* Number of real arguments. */ +    Tcl_Obj *const *objv,	/* The real arguments. */ +    int toRewrite,		/* Number of real arguments to replace. */ +    int rewriteLength,		/* Number of arguments to insert instead. */ +    Tcl_Obj *const *rewriteObjs,/* Arguments to insert instead. */ +    int *lengthPtr)		/* Where to write the resulting length of the +				 * array of rewritten arguments. */ +{ +    Interp *iPtr = (Interp *) interp; +    int isRootEnsemble = (iPtr->ensembleRewrite.sourceObjs == NULL); +    Tcl_Obj **argObjs; +    unsigned len = rewriteLength + objc - toRewrite; + +    argObjs = TclStackAlloc(interp, sizeof(Tcl_Obj *) * len); +    memcpy(argObjs, rewriteObjs, rewriteLength * sizeof(Tcl_Obj *)); +    memcpy(argObjs + rewriteLength, objv + toRewrite, +	    sizeof(Tcl_Obj *) * (objc - toRewrite)); + +    /* +     * Now plumb this into the core ensemble rewrite logging system so that +     * Tcl_WrongNumArgs() can rewrite its result appropriately. The rules for +     * how to store the rewrite rules get complex solely because of the case +     * where an ensemble rewrites itself out of the picture; when that +     * happens, the quality of the error message rewrite falls drastically +     * (and unavoidably). +     */ + +    if (isRootEnsemble) { +	iPtr->ensembleRewrite.sourceObjs = objv; +	iPtr->ensembleRewrite.numRemovedObjs = toRewrite; +	iPtr->ensembleRewrite.numInsertedObjs = rewriteLength; +    } else { +	int numIns = iPtr->ensembleRewrite.numInsertedObjs; + +	if (numIns < toRewrite) { +	    iPtr->ensembleRewrite.numRemovedObjs += toRewrite - numIns; +	    iPtr->ensembleRewrite.numInsertedObjs += rewriteLength - 1; +	} else { +	    iPtr->ensembleRewrite.numInsertedObjs += +		    rewriteLength - toRewrite; +	} +    } + +    *lengthPtr = len; +    return argObjs; +} + +/* + * ---------------------------------------------------------------------- + * + * assorted trivial 'getter' functions + * + * ---------------------------------------------------------------------- + */ + +Tcl_Object +Tcl_MethodDeclarerObject( +    Tcl_Method method) +{ +    return (Tcl_Object) ((Method *) method)->declaringObjectPtr; +} + +Tcl_Class +Tcl_MethodDeclarerClass( +    Tcl_Method method) +{ +    return (Tcl_Class) ((Method *) method)->declaringClassPtr; +} + +Tcl_Obj * +Tcl_MethodName( +    Tcl_Method method) +{ +    return ((Method *) method)->namePtr; +} + +int +Tcl_MethodIsType( +    Tcl_Method method, +    const Tcl_MethodType *typePtr, +    ClientData *clientDataPtr) +{ +    Method *mPtr = (Method *) method; + +    if (mPtr->typePtr == typePtr) { +	if (clientDataPtr != NULL) { +	    *clientDataPtr = mPtr->clientData; +	} +	return 1; +    } +    return 0; +} + +int +Tcl_MethodIsPublic( +    Tcl_Method method) +{ +    return (((Method *)method)->flags & PUBLIC_METHOD) ? 1 : 0; +} + +/* + * Extended method construction for itcl-ng. + */ + +Tcl_Method +TclOONewProcInstanceMethodEx( +    Tcl_Interp *interp,		/* The interpreter containing the object. */ +    Tcl_Object oPtr,		/* The object to modify. */ +    TclOO_PreCallProc *preCallPtr, +    TclOO_PostCallProc *postCallPtr, +    ProcErrorProc *errProc, +    ClientData clientData, +    Tcl_Obj *nameObj,		/* The name of the method, which must not be +				 * NULL. */ +    Tcl_Obj *argsObj,		/* The formal argument list for the method, +				 * which must not be NULL. */ +    Tcl_Obj *bodyObj,		/* The body of the method, which must not be +				 * NULL. */ +    int flags,			/* Whether this is a public method. */ +    void **internalTokenPtr)	/* If non-NULL, points to a variable that gets +				 * the reference to the ProcedureMethod +				 * structure. */ +{ +    ProcedureMethod *pmPtr; +    Tcl_Method method = (Tcl_Method) TclOONewProcInstanceMethod(interp, +	    (Object *) oPtr, flags, nameObj, argsObj, bodyObj, &pmPtr); + +    if (method == NULL) { +	return NULL; +    } +    pmPtr->flags = flags & USE_DECLARER_NS; +    pmPtr->preCallProc = preCallPtr; +    pmPtr->postCallProc = postCallPtr; +    pmPtr->errProc = errProc; +    pmPtr->clientData = clientData; +    if (internalTokenPtr != NULL) { +	*internalTokenPtr = pmPtr; +    } +    return method; +} + +Tcl_Method +TclOONewProcMethodEx( +    Tcl_Interp *interp,		/* The interpreter containing the class. */ +    Tcl_Class clsPtr,		/* The class to modify. */ +    TclOO_PreCallProc *preCallPtr, +    TclOO_PostCallProc *postCallPtr, +    ProcErrorProc *errProc, +    ClientData clientData, +    Tcl_Obj *nameObj,		/* The name of the method, which may be NULL; +				 * if so, up to caller to manage storage +				 * (e.g., because it is a constructor or +				 * destructor). */ +    Tcl_Obj *argsObj,		/* The formal argument list for the method, +				 * which may be NULL; if so, it is equivalent +				 * to an empty list. */ +    Tcl_Obj *bodyObj,		/* The body of the method, which must not be +				 * NULL. */ +    int flags,			/* Whether this is a public method. */ +    void **internalTokenPtr)	/* If non-NULL, points to a variable that gets +				 * the reference to the ProcedureMethod +				 * structure. */ +{ +    ProcedureMethod *pmPtr; +    Tcl_Method method = (Tcl_Method) TclOONewProcMethod(interp, +	    (Class *) clsPtr, flags, nameObj, argsObj, bodyObj, &pmPtr); + +    if (method == NULL) { +	return NULL; +    } +    pmPtr->flags = flags & USE_DECLARER_NS; +    pmPtr->preCallProc = preCallPtr; +    pmPtr->postCallProc = postCallPtr; +    pmPtr->errProc = errProc; +    pmPtr->clientData = clientData; +    if (internalTokenPtr != NULL) { +	*internalTokenPtr = pmPtr; +    } +    return method; +} + +/* + * Local Variables: + * mode: c + * c-basic-offset: 4 + * fill-column: 78 + * End: + */  | 
