3038 lines
82 KiB
C
3038 lines
82 KiB
C
|
/*
|
|||
|
* tclOO.c --
|
|||
|
*
|
|||
|
* This file contains the object-system core (NB: not Tcl_Obj, but ::oo)
|
|||
|
*
|
|||
|
* Copyright (c) 2005-2012 by Donal K. Fellows
|
|||
|
* Copyright (c) 2017 by Nathan Coulter
|
|||
|
*
|
|||
|
* 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"
|
|||
|
|
|||
|
/*
|
|||
|
* Commands in oo::define.
|
|||
|
*/
|
|||
|
|
|||
|
static const struct {
|
|||
|
const char *name;
|
|||
|
Tcl_ObjCmdProc *objProc;
|
|||
|
int flag;
|
|||
|
} defineCmds[] = {
|
|||
|
{"constructor", TclOODefineConstructorObjCmd, 0},
|
|||
|
{"deletemethod", TclOODefineDeleteMethodObjCmd, 0},
|
|||
|
{"destructor", TclOODefineDestructorObjCmd, 0},
|
|||
|
{"export", TclOODefineExportObjCmd, 0},
|
|||
|
{"forward", TclOODefineForwardObjCmd, 0},
|
|||
|
{"method", TclOODefineMethodObjCmd, 0},
|
|||
|
{"renamemethod", TclOODefineRenameMethodObjCmd, 0},
|
|||
|
{"self", TclOODefineSelfObjCmd, 0},
|
|||
|
{"unexport", TclOODefineUnexportObjCmd, 0},
|
|||
|
{NULL, NULL, 0}
|
|||
|
}, objdefCmds[] = {
|
|||
|
{"class", TclOODefineClassObjCmd, 1},
|
|||
|
{"deletemethod", TclOODefineDeleteMethodObjCmd, 1},
|
|||
|
{"export", TclOODefineExportObjCmd, 1},
|
|||
|
{"forward", TclOODefineForwardObjCmd, 1},
|
|||
|
{"method", TclOODefineMethodObjCmd, 1},
|
|||
|
{"renamemethod", TclOODefineRenameMethodObjCmd, 1},
|
|||
|
{"unexport", TclOODefineUnexportObjCmd, 1},
|
|||
|
{NULL, NULL, 0}
|
|||
|
};
|
|||
|
|
|||
|
/*
|
|||
|
* What sort of size of things we like to allocate.
|
|||
|
*/
|
|||
|
|
|||
|
#define ALLOC_CHUNK 8
|
|||
|
|
|||
|
/*
|
|||
|
* Function declarations for things defined in this file.
|
|||
|
*/
|
|||
|
|
|||
|
static Object * AllocObject(Tcl_Interp *interp, const char *nameStr,
|
|||
|
Namespace *nsPtr, const char *nsNameStr);
|
|||
|
static int CloneClassMethod(Tcl_Interp *interp, Class *clsPtr,
|
|||
|
Method *mPtr, Tcl_Obj *namePtr,
|
|||
|
Method **newMPtrPtr);
|
|||
|
static int CloneObjectMethod(Tcl_Interp *interp, Object *oPtr,
|
|||
|
Method *mPtr, Tcl_Obj *namePtr);
|
|||
|
static void DeletedDefineNamespace(ClientData clientData);
|
|||
|
static void DeletedObjdefNamespace(ClientData clientData);
|
|||
|
static void DeletedHelpersNamespace(ClientData clientData);
|
|||
|
static Tcl_NRPostProc FinalizeAlloc;
|
|||
|
static Tcl_NRPostProc FinalizeNext;
|
|||
|
static Tcl_NRPostProc FinalizeObjectCall;
|
|||
|
static void initClassPath(Tcl_Interp * interp, Class *clsPtr);
|
|||
|
static int InitFoundation(Tcl_Interp *interp);
|
|||
|
static void KillFoundation(ClientData clientData,
|
|||
|
Tcl_Interp *interp);
|
|||
|
static void MyDeleted(ClientData clientData);
|
|||
|
static void ObjectNamespaceDeleted(ClientData clientData);
|
|||
|
static void ObjectRenamedTrace(ClientData clientData,
|
|||
|
Tcl_Interp *interp, const char *oldName,
|
|||
|
const char *newName, int flags);
|
|||
|
static inline void SquelchCachedName(Object *oPtr);
|
|||
|
|
|||
|
static int PublicObjectCmd(ClientData clientData,
|
|||
|
Tcl_Interp *interp, int objc,
|
|||
|
Tcl_Obj *const *objv);
|
|||
|
static int PublicNRObjectCmd(ClientData clientData,
|
|||
|
Tcl_Interp *interp, int objc,
|
|||
|
Tcl_Obj *const *objv);
|
|||
|
static int PrivateObjectCmd(ClientData clientData,
|
|||
|
Tcl_Interp *interp, int objc,
|
|||
|
Tcl_Obj *const *objv);
|
|||
|
static int PrivateNRObjectCmd(ClientData clientData,
|
|||
|
Tcl_Interp *interp, int objc,
|
|||
|
Tcl_Obj *const *objv);
|
|||
|
static void RemoveClass(Class ** list, int num, int idx);
|
|||
|
static void RemoveObject(Object ** list, int num, int idx);
|
|||
|
|
|||
|
/*
|
|||
|
* Methods in the oo::object and oo::class classes. First, we define a helper
|
|||
|
* macro that makes building the method type declaration structure a lot
|
|||
|
* easier. No point in making life harder than it has to be!
|
|||
|
*
|
|||
|
* Note that the core methods don't need clone or free proc callbacks.
|
|||
|
*/
|
|||
|
|
|||
|
#define DCM(name,visibility,proc) \
|
|||
|
{name,visibility,\
|
|||
|
{TCL_OO_METHOD_VERSION_CURRENT,"core method: "#name,proc,NULL,NULL}}
|
|||
|
|
|||
|
static const DeclaredClassMethod objMethods[] = {
|
|||
|
DCM("destroy", 1, TclOO_Object_Destroy),
|
|||
|
DCM("eval", 0, TclOO_Object_Eval),
|
|||
|
DCM("unknown", 0, TclOO_Object_Unknown),
|
|||
|
DCM("variable", 0, TclOO_Object_LinkVar),
|
|||
|
DCM("varname", 0, TclOO_Object_VarName),
|
|||
|
{NULL, 0, {0, NULL, NULL, NULL, NULL}}
|
|||
|
}, clsMethods[] = {
|
|||
|
DCM("create", 1, TclOO_Class_Create),
|
|||
|
DCM("new", 1, TclOO_Class_New),
|
|||
|
DCM("createWithNamespace", 0, TclOO_Class_CreateNs),
|
|||
|
{NULL, 0, {0, NULL, NULL, NULL, NULL}}
|
|||
|
};
|
|||
|
|
|||
|
/*
|
|||
|
* And for the oo::class constructor...
|
|||
|
*/
|
|||
|
|
|||
|
static const Tcl_MethodType classConstructor = {
|
|||
|
TCL_OO_METHOD_VERSION_CURRENT,
|
|||
|
"oo::class constructor",
|
|||
|
TclOO_Class_Constructor, NULL, NULL
|
|||
|
};
|
|||
|
|
|||
|
/*
|
|||
|
* Scripted parts of TclOO. First, the main script (cannot be outside this
|
|||
|
* file).
|
|||
|
*/
|
|||
|
|
|||
|
static const char *initScript =
|
|||
|
"package ifneeded TclOO " TCLOO_PATCHLEVEL " {# Already present, OK?};"
|
|||
|
"namespace eval ::oo { variable version " TCLOO_VERSION " };"
|
|||
|
"namespace eval ::oo { variable patchlevel " TCLOO_PATCHLEVEL " };";
|
|||
|
/* "tcl_findLibrary tcloo $oo::version $oo::version" */
|
|||
|
/* " tcloo.tcl OO_LIBRARY oo::library;"; */
|
|||
|
|
|||
|
/*
|
|||
|
* The scripted part of the definitions of slots.
|
|||
|
*/
|
|||
|
|
|||
|
static const char *slotScript =
|
|||
|
"::oo::define ::oo::Slot {\n"
|
|||
|
" method Get {} {error unimplemented}\n"
|
|||
|
" method Set list {error unimplemented}\n"
|
|||
|
" method -set args {\n"
|
|||
|
" uplevel 1 [list [namespace which my] Set $args]\n"
|
|||
|
" }\n"
|
|||
|
" method -append args {\n"
|
|||
|
" uplevel 1 [list [namespace which my] Set [list"
|
|||
|
" {*}[uplevel 1 [list [namespace which my] Get]] {*}$args]]\n"
|
|||
|
" }\n"
|
|||
|
" method -clear {} {uplevel 1 [list [namespace which my] Set {}]}\n"
|
|||
|
" forward --default-operation my -append\n"
|
|||
|
" method unknown {args} {\n"
|
|||
|
" set def --default-operation\n"
|
|||
|
" if {[llength $args] == 0} {\n"
|
|||
|
" return [uplevel 1 [list [namespace which my] $def]]\n"
|
|||
|
" } elseif {![string match -* [lindex $args 0]]} {\n"
|
|||
|
" return [uplevel 1 [list [namespace which my] $def {*}$args]]\n"
|
|||
|
" }\n"
|
|||
|
" next {*}$args\n"
|
|||
|
" }\n"
|
|||
|
" export -set -append -clear\n"
|
|||
|
" unexport unknown destroy\n"
|
|||
|
"}\n"
|
|||
|
"::oo::objdefine ::oo::define::superclass forward --default-operation my -set\n"
|
|||
|
"::oo::objdefine ::oo::define::mixin forward --default-operation my -set\n"
|
|||
|
"::oo::objdefine ::oo::objdefine::mixin forward --default-operation my -set\n";
|
|||
|
|
|||
|
/*
|
|||
|
* The body of the <cloned> method of oo::object.
|
|||
|
*/
|
|||
|
|
|||
|
static const char *clonedBody =
|
|||
|
"foreach p [info procs [info object namespace $originObject]::*] {"
|
|||
|
" set args [info args $p];"
|
|||
|
" set idx -1;"
|
|||
|
" foreach a $args {"
|
|||
|
" lset args [incr idx] "
|
|||
|
" [if {[info default $p $a d]} {list $a $d} {list $a}]"
|
|||
|
" };"
|
|||
|
" set b [info body $p];"
|
|||
|
" set p [namespace tail $p];"
|
|||
|
" proc $p $args $b;"
|
|||
|
"};"
|
|||
|
"foreach v [info vars [info object namespace $originObject]::*] {"
|
|||
|
" upvar 0 $v vOrigin;"
|
|||
|
" namespace upvar [namespace current] [namespace tail $v] vNew;"
|
|||
|
" if {[info exists vOrigin]} {"
|
|||
|
" if {[array exists vOrigin]} {"
|
|||
|
" array set vNew [array get vOrigin];"
|
|||
|
" } else {"
|
|||
|
" set vNew $vOrigin;"
|
|||
|
" }"
|
|||
|
" }"
|
|||
|
"}";
|
|||
|
|
|||
|
/*
|
|||
|
* The actual definition of the variable holding the TclOO stub table.
|
|||
|
*/
|
|||
|
|
|||
|
MODULE_SCOPE const TclOOStubs tclOOStubs;
|
|||
|
|
|||
|
/*
|
|||
|
* Convenience macro for getting the foundation from an interpreter.
|
|||
|
*/
|
|||
|
|
|||
|
#define GetFoundation(interp) \
|
|||
|
((Foundation *)((Interp *)(interp))->objectFoundation)
|
|||
|
|
|||
|
/*
|
|||
|
* Macros to make inspecting into the guts of an object cleaner.
|
|||
|
*
|
|||
|
* The ocPtr parameter (only in these macros) is assumed to work fine with
|
|||
|
* either an oPtr or a classPtr. Note that the roots oo::object and oo::class
|
|||
|
* have _both_ their object and class flags tagged with ROOT_OBJECT and
|
|||
|
* ROOT_CLASS respectively.
|
|||
|
*/
|
|||
|
|
|||
|
#define Destructing(oPtr) ((oPtr)->flags & OBJECT_DESTRUCTING)
|
|||
|
#define IsRootObject(ocPtr) ((ocPtr)->flags & ROOT_OBJECT)
|
|||
|
#define IsRootClass(ocPtr) ((ocPtr)->flags & ROOT_CLASS)
|
|||
|
#define IsRoot(ocPtr) ((ocPtr)->flags & (ROOT_OBJECT|ROOT_CLASS))
|
|||
|
|
|||
|
#define RemoveItem(type, lst, i) \
|
|||
|
do { \
|
|||
|
Remove ## type ((lst).list, (lst).num, i); \
|
|||
|
(lst).num--; \
|
|||
|
} while (0)
|
|||
|
|
|||
|
/*
|
|||
|
* ----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* TclOOInit --
|
|||
|
*
|
|||
|
* Called to initialise the OO system within an interpreter.
|
|||
|
*
|
|||
|
* Result:
|
|||
|
* TCL_OK if the setup succeeded. Currently assumed to always work.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* Creates namespaces, commands, several classes and a number of
|
|||
|
* callbacks. Upon return, the OO system is ready for use.
|
|||
|
*
|
|||
|
* ----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
int
|
|||
|
TclOOInit(
|
|||
|
Tcl_Interp *interp) /* The interpreter to install into. */
|
|||
|
{
|
|||
|
/*
|
|||
|
* Build the core of the OO system.
|
|||
|
*/
|
|||
|
|
|||
|
if (InitFoundation(interp) != TCL_OK) {
|
|||
|
return TCL_ERROR;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Run our initialization script and, if that works, declare the package
|
|||
|
* to be fully provided.
|
|||
|
*/
|
|||
|
|
|||
|
if (Tcl_Eval(interp, initScript) != TCL_OK) {
|
|||
|
return TCL_ERROR;
|
|||
|
}
|
|||
|
|
|||
|
return Tcl_PkgProvideEx(interp, "TclOO", TCLOO_PATCHLEVEL,
|
|||
|
(ClientData) &tclOOStubs);
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* ----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* TclOOGetFoundation --
|
|||
|
*
|
|||
|
* Get a reference to the OO core class system.
|
|||
|
*
|
|||
|
* ----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
Foundation *
|
|||
|
TclOOGetFoundation(
|
|||
|
Tcl_Interp *interp)
|
|||
|
{
|
|||
|
return GetFoundation(interp);
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* ----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* InitFoundation --
|
|||
|
*
|
|||
|
* Set up the core of the OO core class system. This is a structure
|
|||
|
* holding references to the magical bits that need to be known about in
|
|||
|
* other places, plus the oo::object and oo::class classes.
|
|||
|
*
|
|||
|
* ----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
static int
|
|||
|
InitFoundation(
|
|||
|
Tcl_Interp *interp)
|
|||
|
{
|
|||
|
static Tcl_ThreadDataKey tsdKey;
|
|||
|
ThreadLocalData *tsdPtr =
|
|||
|
Tcl_GetThreadData(&tsdKey, sizeof(ThreadLocalData));
|
|||
|
Foundation *fPtr = ckalloc(sizeof(Foundation));
|
|||
|
Tcl_Obj *namePtr, *argsPtr, *bodyPtr;
|
|||
|
|
|||
|
Class fakeCls;
|
|||
|
Object fakeObject;
|
|||
|
|
|||
|
Tcl_DString buffer;
|
|||
|
Command *cmdPtr;
|
|||
|
int i;
|
|||
|
|
|||
|
/*
|
|||
|
* Initialize the structure that holds the OO system core. This is
|
|||
|
* attached to the interpreter via an assocData entry; not very efficient,
|
|||
|
* but the best we can do without hacking the core more.
|
|||
|
*/
|
|||
|
|
|||
|
memset(fPtr, 0, sizeof(Foundation));
|
|||
|
((Interp *) interp)->objectFoundation = fPtr;
|
|||
|
fPtr->interp = interp;
|
|||
|
fPtr->ooNs = Tcl_CreateNamespace(interp, "::oo", fPtr, NULL);
|
|||
|
Tcl_Export(interp, fPtr->ooNs, "[a-z]*", 1);
|
|||
|
fPtr->defineNs = Tcl_CreateNamespace(interp, "::oo::define", fPtr,
|
|||
|
DeletedDefineNamespace);
|
|||
|
fPtr->objdefNs = Tcl_CreateNamespace(interp, "::oo::objdefine", fPtr,
|
|||
|
DeletedObjdefNamespace);
|
|||
|
fPtr->helpersNs = Tcl_CreateNamespace(interp, "::oo::Helpers", fPtr,
|
|||
|
DeletedHelpersNamespace);
|
|||
|
fPtr->epoch = 0;
|
|||
|
fPtr->tsdPtr = tsdPtr;
|
|||
|
TclNewLiteralStringObj(fPtr->unknownMethodNameObj, "unknown");
|
|||
|
TclNewLiteralStringObj(fPtr->constructorName, "<constructor>");
|
|||
|
TclNewLiteralStringObj(fPtr->destructorName, "<destructor>");
|
|||
|
TclNewLiteralStringObj(fPtr->clonedName, "<cloned>");
|
|||
|
TclNewLiteralStringObj(fPtr->defineName, "::oo::define");
|
|||
|
Tcl_IncrRefCount(fPtr->unknownMethodNameObj);
|
|||
|
Tcl_IncrRefCount(fPtr->constructorName);
|
|||
|
Tcl_IncrRefCount(fPtr->destructorName);
|
|||
|
Tcl_IncrRefCount(fPtr->clonedName);
|
|||
|
Tcl_IncrRefCount(fPtr->defineName);
|
|||
|
Tcl_CreateObjCommand(interp, "::oo::UnknownDefinition",
|
|||
|
TclOOUnknownDefinition, NULL, NULL);
|
|||
|
TclNewLiteralStringObj(namePtr, "::oo::UnknownDefinition");
|
|||
|
Tcl_SetNamespaceUnknownHandler(interp, fPtr->defineNs, namePtr);
|
|||
|
Tcl_SetNamespaceUnknownHandler(interp, fPtr->objdefNs, namePtr);
|
|||
|
|
|||
|
/*
|
|||
|
* Create the subcommands in the oo::define and oo::objdefine spaces.
|
|||
|
*/
|
|||
|
|
|||
|
Tcl_DStringInit(&buffer);
|
|||
|
for (i = 0 ; defineCmds[i].name ; i++) {
|
|||
|
TclDStringAppendLiteral(&buffer, "::oo::define::");
|
|||
|
Tcl_DStringAppend(&buffer, defineCmds[i].name, -1);
|
|||
|
Tcl_CreateObjCommand(interp, Tcl_DStringValue(&buffer),
|
|||
|
defineCmds[i].objProc, INT2PTR(defineCmds[i].flag), NULL);
|
|||
|
Tcl_DStringFree(&buffer);
|
|||
|
}
|
|||
|
for (i = 0 ; objdefCmds[i].name ; i++) {
|
|||
|
TclDStringAppendLiteral(&buffer, "::oo::objdefine::");
|
|||
|
Tcl_DStringAppend(&buffer, objdefCmds[i].name, -1);
|
|||
|
Tcl_CreateObjCommand(interp, Tcl_DStringValue(&buffer),
|
|||
|
objdefCmds[i].objProc, INT2PTR(objdefCmds[i].flag), NULL);
|
|||
|
Tcl_DStringFree(&buffer);
|
|||
|
}
|
|||
|
|
|||
|
Tcl_CallWhenDeleted(interp, KillFoundation, NULL);
|
|||
|
|
|||
|
/*
|
|||
|
* Create the objects at the core of the object system. These need to be
|
|||
|
* spliced manually.
|
|||
|
*/
|
|||
|
|
|||
|
/*
|
|||
|
* Stand up a phony class for bootstrapping.
|
|||
|
*/
|
|||
|
|
|||
|
fPtr->objectCls = &fakeCls;
|
|||
|
|
|||
|
/*
|
|||
|
* Referenced in TclOOAllocClass to increment the refCount.
|
|||
|
*/
|
|||
|
|
|||
|
fakeCls.thisPtr = &fakeObject;
|
|||
|
|
|||
|
fPtr->objectCls = TclOOAllocClass(interp,
|
|||
|
AllocObject(interp, "object", (Namespace *)fPtr->ooNs, NULL));
|
|||
|
/*
|
|||
|
* Corresponding TclOODecrRefCount in KillFoudation.
|
|||
|
*/
|
|||
|
|
|||
|
AddRef(fPtr->objectCls->thisPtr);
|
|||
|
|
|||
|
/*
|
|||
|
* This is why it is unnecessary in this routine to replace the
|
|||
|
* incremented reference count of fPtr->objectCls that was swallowed by
|
|||
|
* fakeObject.
|
|||
|
*/
|
|||
|
|
|||
|
fPtr->objectCls->superclasses.num = 0;
|
|||
|
ckfree(fPtr->objectCls->superclasses.list);
|
|||
|
fPtr->objectCls->superclasses.list = NULL;
|
|||
|
|
|||
|
/*
|
|||
|
* Special initialization for the primordial objects.
|
|||
|
*/
|
|||
|
|
|||
|
fPtr->objectCls->thisPtr->flags |= ROOT_OBJECT;
|
|||
|
fPtr->objectCls->flags |= ROOT_OBJECT;
|
|||
|
|
|||
|
fPtr->classCls = TclOOAllocClass(interp,
|
|||
|
AllocObject(interp, "class", (Namespace *)fPtr->ooNs, NULL));
|
|||
|
|
|||
|
/*
|
|||
|
* Corresponding TclOODecrRefCount in KillFoudation.
|
|||
|
*/
|
|||
|
|
|||
|
AddRef(fPtr->classCls->thisPtr);
|
|||
|
|
|||
|
/*
|
|||
|
* Increment reference counts for each reference because these
|
|||
|
* relationships can be dynamically changed.
|
|||
|
*
|
|||
|
* Corresponding TclOODecrRefCount for all incremented refcounts is in
|
|||
|
* KillFoundation.
|
|||
|
*/
|
|||
|
|
|||
|
/*
|
|||
|
* Rewire bootstrapped objects.
|
|||
|
*/
|
|||
|
|
|||
|
fPtr->objectCls->thisPtr->selfCls = fPtr->classCls;
|
|||
|
AddRef(fPtr->classCls->thisPtr);
|
|||
|
TclOOAddToInstances(fPtr->objectCls->thisPtr, fPtr->classCls);
|
|||
|
|
|||
|
fPtr->classCls->thisPtr->selfCls = fPtr->classCls;
|
|||
|
AddRef(fPtr->classCls->thisPtr);
|
|||
|
TclOOAddToInstances(fPtr->classCls->thisPtr, fPtr->classCls);
|
|||
|
|
|||
|
fPtr->classCls->thisPtr->flags |= ROOT_CLASS;
|
|||
|
fPtr->classCls->flags |= ROOT_CLASS;
|
|||
|
|
|||
|
/*
|
|||
|
* Standard initialization for new Objects.
|
|||
|
*/
|
|||
|
|
|||
|
TclOOAddToSubclasses(fPtr->classCls, fPtr->objectCls);
|
|||
|
|
|||
|
/*
|
|||
|
* Basic method declarations for the core classes.
|
|||
|
*/
|
|||
|
|
|||
|
for (i = 0 ; objMethods[i].name ; i++) {
|
|||
|
TclOONewBasicMethod(interp, fPtr->objectCls, &objMethods[i]);
|
|||
|
}
|
|||
|
for (i = 0 ; clsMethods[i].name ; i++) {
|
|||
|
TclOONewBasicMethod(interp, fPtr->classCls, &clsMethods[i]);
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Create the default <cloned> method implementation, used when 'oo::copy'
|
|||
|
* is called to finish the copying of one object to another.
|
|||
|
*/
|
|||
|
|
|||
|
TclNewLiteralStringObj(argsPtr, "originObject");
|
|||
|
Tcl_IncrRefCount(argsPtr);
|
|||
|
bodyPtr = Tcl_NewStringObj(clonedBody, -1);
|
|||
|
TclOONewProcMethod(interp, fPtr->objectCls, 0, fPtr->clonedName, argsPtr,
|
|||
|
bodyPtr, NULL);
|
|||
|
TclDecrRefCount(argsPtr);
|
|||
|
|
|||
|
/*
|
|||
|
* Finish setting up the class of classes by marking the 'new' method as
|
|||
|
* private; classes, unlike general objects, must have explicit names. We
|
|||
|
* also need to create the constructor for classes.
|
|||
|
*/
|
|||
|
|
|||
|
TclNewLiteralStringObj(namePtr, "new");
|
|||
|
Tcl_NewInstanceMethod(interp, (Tcl_Object) fPtr->classCls->thisPtr,
|
|||
|
namePtr /* keeps ref */, 0 /* private */, NULL, NULL);
|
|||
|
fPtr->classCls->constructorPtr = (Method *) Tcl_NewMethod(interp,
|
|||
|
(Tcl_Class) fPtr->classCls, NULL, 0, &classConstructor, NULL);
|
|||
|
|
|||
|
/*
|
|||
|
* Create non-object commands and plug ourselves into the Tcl [info]
|
|||
|
* ensemble.
|
|||
|
*/
|
|||
|
|
|||
|
cmdPtr = (Command *) Tcl_NRCreateCommand(interp, "::oo::Helpers::next",
|
|||
|
NULL, TclOONextObjCmd, NULL, NULL);
|
|||
|
cmdPtr->compileProc = TclCompileObjectNextCmd;
|
|||
|
cmdPtr = (Command *) Tcl_NRCreateCommand(interp, "::oo::Helpers::nextto",
|
|||
|
NULL, TclOONextToObjCmd, NULL, NULL);
|
|||
|
cmdPtr->compileProc = TclCompileObjectNextToCmd;
|
|||
|
cmdPtr = (Command *) Tcl_CreateObjCommand(interp, "::oo::Helpers::self",
|
|||
|
TclOOSelfObjCmd, NULL, NULL);
|
|||
|
cmdPtr->compileProc = TclCompileObjectSelfCmd;
|
|||
|
Tcl_CreateObjCommand(interp, "::oo::define", TclOODefineObjCmd, NULL,
|
|||
|
NULL);
|
|||
|
Tcl_CreateObjCommand(interp, "::oo::objdefine", TclOOObjDefObjCmd, NULL,
|
|||
|
NULL);
|
|||
|
Tcl_CreateObjCommand(interp, "::oo::copy", TclOOCopyObjectCmd, NULL,NULL);
|
|||
|
TclOOInitInfo(interp);
|
|||
|
|
|||
|
/*
|
|||
|
* Now make the class of slots.
|
|||
|
*/
|
|||
|
|
|||
|
if (TclOODefineSlots(fPtr) != TCL_OK) {
|
|||
|
return TCL_ERROR;
|
|||
|
}
|
|||
|
return Tcl_Eval(interp, slotScript);
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* ----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* DeletedDefineNamespace, DeletedObjdefNamespace, DeletedHelpersNamespace --
|
|||
|
*
|
|||
|
* Simple helpers used to clear fields of the foundation when they no
|
|||
|
* longer hold useful information.
|
|||
|
*
|
|||
|
* ----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
static void
|
|||
|
DeletedDefineNamespace(
|
|||
|
ClientData clientData)
|
|||
|
{
|
|||
|
Foundation *fPtr = clientData;
|
|||
|
|
|||
|
fPtr->defineNs = NULL;
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
DeletedObjdefNamespace(
|
|||
|
ClientData clientData)
|
|||
|
{
|
|||
|
Foundation *fPtr = clientData;
|
|||
|
|
|||
|
fPtr->objdefNs = NULL;
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
DeletedHelpersNamespace(
|
|||
|
ClientData clientData)
|
|||
|
{
|
|||
|
Foundation *fPtr = clientData;
|
|||
|
|
|||
|
fPtr->helpersNs = NULL;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* ----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* KillFoundation --
|
|||
|
*
|
|||
|
* Delete those parts of the OO core that are not deleted automatically
|
|||
|
* when the objects and classes themselves are destroyed.
|
|||
|
*
|
|||
|
* ----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
static void
|
|||
|
KillFoundation(
|
|||
|
ClientData clientData, /* Pointer to the OO system foundation
|
|||
|
* structure. */
|
|||
|
Tcl_Interp *interp) /* The interpreter containing the OO system
|
|||
|
* foundation. */
|
|||
|
{
|
|||
|
Foundation *fPtr = GetFoundation(interp);
|
|||
|
|
|||
|
TclDecrRefCount(fPtr->unknownMethodNameObj);
|
|||
|
TclDecrRefCount(fPtr->constructorName);
|
|||
|
TclDecrRefCount(fPtr->destructorName);
|
|||
|
TclDecrRefCount(fPtr->clonedName);
|
|||
|
TclDecrRefCount(fPtr->defineName);
|
|||
|
TclOODecrRefCount(fPtr->objectCls->thisPtr);
|
|||
|
TclOODecrRefCount(fPtr->classCls->thisPtr);
|
|||
|
|
|||
|
ckfree(fPtr);
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* ----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* AllocObject --
|
|||
|
*
|
|||
|
* Allocate an object of basic type. Does not splice the object into its
|
|||
|
* class's instance list. The caller must set the classPtr on the object
|
|||
|
* to either a class or NULL, call TclOOAddToInstances to add the object
|
|||
|
* to the class's instance list, and if the object itself is a class, use
|
|||
|
* call TclOOAddToSubclasses() to add it to the right class's list of
|
|||
|
* subclasses.
|
|||
|
*
|
|||
|
* ----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
static Object *
|
|||
|
AllocObject(
|
|||
|
Tcl_Interp *interp, /* Interpreter within which to create the
|
|||
|
* object. */
|
|||
|
const char *nameStr, /* The name of the object to create, or NULL
|
|||
|
* if the OO system should pick the object
|
|||
|
* name itself (equal to the namespace
|
|||
|
* name). */
|
|||
|
Namespace *nsPtr, /* The namespace to create the object in,
|
|||
|
or NULL if *nameStr is NULL */
|
|||
|
const char *nsNameStr) /* The name of the namespace to create, or
|
|||
|
* NULL if the OO system should pick a unique
|
|||
|
* name itself. If this is non-NULL but names
|
|||
|
* a namespace that already exists, the effect
|
|||
|
* will be the same as if this was NULL. */
|
|||
|
{
|
|||
|
Foundation *fPtr = GetFoundation(interp);
|
|||
|
Object *oPtr;
|
|||
|
Command *cmdPtr;
|
|||
|
CommandTrace *tracePtr;
|
|||
|
int creationEpoch;
|
|||
|
|
|||
|
oPtr = ckalloc(sizeof(Object));
|
|||
|
memset(oPtr, 0, sizeof(Object));
|
|||
|
|
|||
|
/*
|
|||
|
* Every object has a namespace; make one. Note that this also normally
|
|||
|
* computes the creation epoch value for the object, a sequence number
|
|||
|
* that is unique to the object (and which allows us to manage method
|
|||
|
* caching without comparing pointers).
|
|||
|
*
|
|||
|
* When creating a namespace, we first check to see if the caller
|
|||
|
* specified the name for the namespace. If not, we generate namespace
|
|||
|
* names using the epoch until such time as a new namespace is actually
|
|||
|
* created.
|
|||
|
*/
|
|||
|
|
|||
|
if (nsNameStr != NULL) {
|
|||
|
oPtr->namespacePtr = Tcl_CreateNamespace(interp, nsNameStr, oPtr, NULL);
|
|||
|
if (oPtr->namespacePtr != NULL) {
|
|||
|
creationEpoch = ++fPtr->tsdPtr->nsCount;
|
|||
|
goto configNamespace;
|
|||
|
}
|
|||
|
Tcl_ResetResult(interp);
|
|||
|
}
|
|||
|
|
|||
|
while (1) {
|
|||
|
char objName[10 + TCL_INTEGER_SPACE];
|
|||
|
|
|||
|
sprintf(objName, "::oo::Obj%d", ++fPtr->tsdPtr->nsCount);
|
|||
|
oPtr->namespacePtr = Tcl_CreateNamespace(interp, objName, oPtr, NULL);
|
|||
|
if (oPtr->namespacePtr != NULL) {
|
|||
|
creationEpoch = fPtr->tsdPtr->nsCount;
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Could not make that namespace, so we make another. But first we
|
|||
|
* have to get rid of the error message from Tcl_CreateNamespace,
|
|||
|
* since that's something that should not be exposed to the user.
|
|||
|
*/
|
|||
|
|
|||
|
Tcl_ResetResult(interp);
|
|||
|
}
|
|||
|
|
|||
|
configNamespace:
|
|||
|
((Namespace *) oPtr->namespacePtr)->refCount++;
|
|||
|
|
|||
|
/*
|
|||
|
* Make the namespace know about the helper commands. This grants access
|
|||
|
* to the [self] and [next] commands.
|
|||
|
*/
|
|||
|
|
|||
|
if (fPtr->helpersNs != NULL) {
|
|||
|
TclSetNsPath((Namespace *) oPtr->namespacePtr, 1, &fPtr->helpersNs);
|
|||
|
}
|
|||
|
TclOOSetupVariableResolver(oPtr->namespacePtr);
|
|||
|
|
|||
|
/*
|
|||
|
* Suppress use of compiled versions of the commands in this object's
|
|||
|
* namespace and its children; causes wrong behaviour without expensive
|
|||
|
* recompilation. [Bug 2037727]
|
|||
|
*/
|
|||
|
|
|||
|
((Namespace *) oPtr->namespacePtr)->flags |= NS_SUPPRESS_COMPILATION;
|
|||
|
|
|||
|
/*
|
|||
|
* Set up a callback to get notification of the deletion of a namespace
|
|||
|
* when enough of the namespace still remains to execute commands and
|
|||
|
* access variables in it. [Bug 2950259]
|
|||
|
*/
|
|||
|
|
|||
|
((Namespace *) oPtr->namespacePtr)->earlyDeleteProc = ObjectNamespaceDeleted;
|
|||
|
|
|||
|
/*
|
|||
|
* Fill in the rest of the non-zero/NULL parts of the structure.
|
|||
|
*/
|
|||
|
|
|||
|
oPtr->fPtr = fPtr;
|
|||
|
oPtr->creationEpoch = creationEpoch;
|
|||
|
|
|||
|
/*
|
|||
|
* An object starts life with a refCount of 2 to mark the two stages of
|
|||
|
* destruction it occur: A call to ObjectRenamedTrace(), and a call to
|
|||
|
* ObjectNamespaceDeleted().
|
|||
|
*/
|
|||
|
oPtr->refCount = 2;
|
|||
|
|
|||
|
oPtr->flags = USE_CLASS_CACHE;
|
|||
|
|
|||
|
/*
|
|||
|
* Finally, create the object commands and initialize the trace on the
|
|||
|
* public command (so that the object structures are deleted when the
|
|||
|
* command is deleted).
|
|||
|
*/
|
|||
|
|
|||
|
if (!nameStr) {
|
|||
|
nameStr = oPtr->namespacePtr->name;
|
|||
|
nsPtr = (Namespace *)oPtr->namespacePtr;
|
|||
|
if (nsPtr->parentPtr != NULL) {
|
|||
|
nsPtr = nsPtr->parentPtr;
|
|||
|
}
|
|||
|
|
|||
|
}
|
|||
|
oPtr->command = TclCreateObjCommandInNs(interp, nameStr,
|
|||
|
(Tcl_Namespace *)nsPtr, PublicObjectCmd, oPtr, NULL);
|
|||
|
|
|||
|
/*
|
|||
|
* Add the NRE command and trace directly. While this breaks a number of
|
|||
|
* abstractions, it is faster and we're inside Tcl here so we're allowed.
|
|||
|
*/
|
|||
|
|
|||
|
cmdPtr = (Command *) oPtr->command;
|
|||
|
cmdPtr->nreProc = PublicNRObjectCmd;
|
|||
|
cmdPtr->tracePtr = tracePtr = ckalloc(sizeof(CommandTrace));
|
|||
|
tracePtr->traceProc = ObjectRenamedTrace;
|
|||
|
tracePtr->clientData = oPtr;
|
|||
|
tracePtr->flags = TCL_TRACE_RENAME|TCL_TRACE_DELETE;
|
|||
|
tracePtr->nextPtr = NULL;
|
|||
|
tracePtr->refCount = 1;
|
|||
|
|
|||
|
oPtr->myCommand = TclNRCreateCommandInNs(interp, "my", oPtr->namespacePtr,
|
|||
|
PrivateObjectCmd, PrivateNRObjectCmd, oPtr, MyDeleted);
|
|||
|
return oPtr;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* ----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* SquelchCachedName --
|
|||
|
*
|
|||
|
* Encapsulates how to throw away a cached object name. Called from
|
|||
|
* object rename traces and at object destruction.
|
|||
|
*
|
|||
|
* ----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
static inline void
|
|||
|
SquelchCachedName(
|
|||
|
Object *oPtr)
|
|||
|
{
|
|||
|
if (oPtr->cachedNameObj) {
|
|||
|
Tcl_DecrRefCount(oPtr->cachedNameObj);
|
|||
|
oPtr->cachedNameObj = NULL;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* ----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* MyDeleted --
|
|||
|
*
|
|||
|
* This callback is triggered when the object's [my] command is deleted
|
|||
|
* by any mechanism. It just marks the object as not having a [my]
|
|||
|
* command, and so prevents cleanup of that when the object itself is
|
|||
|
* deleted.
|
|||
|
*
|
|||
|
* ----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
static void
|
|||
|
MyDeleted(
|
|||
|
ClientData clientData) /* Reference to the object whose [my] has been
|
|||
|
* squelched. */
|
|||
|
{
|
|||
|
Object *oPtr = clientData;
|
|||
|
|
|||
|
oPtr->myCommand = NULL;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* ----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* ObjectRenamedTrace --
|
|||
|
*
|
|||
|
* This callback is triggered when the object is deleted by any
|
|||
|
* mechanism. It runs the destructors and arranges for the actual cleanup
|
|||
|
* of the object's namespace, which in turn triggers cleansing of the
|
|||
|
* object data structures.
|
|||
|
*
|
|||
|
* ----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
static void
|
|||
|
ObjectRenamedTrace(
|
|||
|
ClientData clientData, /* The object being deleted. */
|
|||
|
Tcl_Interp *interp, /* The interpreter containing the object. */
|
|||
|
const char *oldName, /* What the object was (last) called. */
|
|||
|
const char *newName, /* What it's getting renamed to. (unused) */
|
|||
|
int flags) /* Why was the object deleted? */
|
|||
|
{
|
|||
|
Object *oPtr = clientData;
|
|||
|
/*
|
|||
|
* If this is a rename and not a delete of the object, we just flush the
|
|||
|
* cache of the object name.
|
|||
|
*/
|
|||
|
|
|||
|
if (flags & TCL_TRACE_RENAME) {
|
|||
|
SquelchCachedName(oPtr);
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* The namespace is only deleted if it hasn't already been deleted. [Bug
|
|||
|
* 2950259].
|
|||
|
*/
|
|||
|
|
|||
|
if (!Destructing(oPtr)) {
|
|||
|
Tcl_DeleteNamespace(oPtr->namespacePtr);
|
|||
|
}
|
|||
|
oPtr->command = NULL;
|
|||
|
TclOODecrRefCount(oPtr);
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* ----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* TclOODeleteDescendants --
|
|||
|
*
|
|||
|
* Delete all descendants of a particular class.
|
|||
|
*
|
|||
|
* ----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
void
|
|||
|
TclOODeleteDescendants(
|
|||
|
Tcl_Interp *interp, /* The interpreter containing the class. */
|
|||
|
Object *oPtr) /* The object representing the class. */
|
|||
|
{
|
|||
|
Class *clsPtr = oPtr->classPtr, *subclassPtr, *mixinSubclassPtr;
|
|||
|
Object *instancePtr;
|
|||
|
|
|||
|
/*
|
|||
|
* Squelch classes that this class has been mixed into.
|
|||
|
*/
|
|||
|
|
|||
|
if (clsPtr->mixinSubs.num > 0) {
|
|||
|
while (clsPtr->mixinSubs.num > 0) {
|
|||
|
mixinSubclassPtr =
|
|||
|
clsPtr->mixinSubs.list[clsPtr->mixinSubs.num - 1];
|
|||
|
|
|||
|
/*
|
|||
|
* This condition also covers the case where mixinSubclassPtr ==
|
|||
|
* clsPtr
|
|||
|
*/
|
|||
|
|
|||
|
if (!Destructing(mixinSubclassPtr->thisPtr)
|
|||
|
&& !(mixinSubclassPtr->thisPtr->flags & DONT_DELETE)) {
|
|||
|
Tcl_DeleteCommandFromToken(interp,
|
|||
|
mixinSubclassPtr->thisPtr->command);
|
|||
|
}
|
|||
|
TclOORemoveFromMixinSubs(mixinSubclassPtr, clsPtr);
|
|||
|
}
|
|||
|
}
|
|||
|
if (clsPtr->mixinSubs.size > 0) {
|
|||
|
ckfree(clsPtr->mixinSubs.list);
|
|||
|
clsPtr->mixinSubs.size = 0;
|
|||
|
}
|
|||
|
/*
|
|||
|
* Squelch subclasses of this class.
|
|||
|
*/
|
|||
|
|
|||
|
if (clsPtr->subclasses.num > 0) {
|
|||
|
while (clsPtr->subclasses.num > 0) {
|
|||
|
subclassPtr = clsPtr->subclasses.list[clsPtr->subclasses.num - 1];
|
|||
|
if (!Destructing(subclassPtr->thisPtr) && !IsRoot(subclassPtr)
|
|||
|
&& !(subclassPtr->thisPtr->flags & DONT_DELETE)) {
|
|||
|
Tcl_DeleteCommandFromToken(interp,
|
|||
|
subclassPtr->thisPtr->command);
|
|||
|
}
|
|||
|
TclOORemoveFromSubclasses(subclassPtr, clsPtr);
|
|||
|
}
|
|||
|
}
|
|||
|
if (clsPtr->subclasses.size > 0) {
|
|||
|
ckfree(clsPtr->subclasses.list);
|
|||
|
clsPtr->subclasses.list = NULL;
|
|||
|
clsPtr->subclasses.size = 0;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Squelch instances of this class (includes objects we're mixed into).
|
|||
|
*/
|
|||
|
|
|||
|
if (clsPtr->instances.num > 0) {
|
|||
|
while (clsPtr->instances.num > 0) {
|
|||
|
instancePtr = clsPtr->instances.list[clsPtr->instances.num - 1];
|
|||
|
|
|||
|
/*
|
|||
|
* This condition also covers the case where instancePtr == oPtr
|
|||
|
*/
|
|||
|
|
|||
|
if (!Destructing(instancePtr) && !IsRoot(instancePtr) &&
|
|||
|
!(instancePtr->flags & DONT_DELETE)) {
|
|||
|
Tcl_DeleteCommandFromToken(interp, instancePtr->command);
|
|||
|
}
|
|||
|
TclOORemoveFromInstances(instancePtr, clsPtr);
|
|||
|
}
|
|||
|
}
|
|||
|
if (clsPtr->instances.size > 0) {
|
|||
|
ckfree(clsPtr->instances.list);
|
|||
|
clsPtr->instances.list = NULL;
|
|||
|
clsPtr->instances.size = 0;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* ----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* TclOOReleaseClassContents --
|
|||
|
*
|
|||
|
* Tear down the special class data structure, including deleting all
|
|||
|
* dependent classes and objects.
|
|||
|
*
|
|||
|
* ----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
void
|
|||
|
TclOOReleaseClassContents(
|
|||
|
Tcl_Interp *interp, /* The interpreter containing the class. */
|
|||
|
Object *oPtr) /* The object representing the class. */
|
|||
|
{
|
|||
|
FOREACH_HASH_DECLS;
|
|||
|
int i;
|
|||
|
Class *clsPtr = oPtr->classPtr, *tmpClsPtr;
|
|||
|
Method *mPtr;
|
|||
|
Foundation *fPtr = oPtr->fPtr;
|
|||
|
Tcl_Obj *variableObj;
|
|||
|
|
|||
|
/*
|
|||
|
* Sanity check!
|
|||
|
*/
|
|||
|
|
|||
|
if (!Destructing(oPtr)) {
|
|||
|
if (IsRootClass(oPtr)) {
|
|||
|
Tcl_Panic("deleting class structure for non-deleted %s",
|
|||
|
"::oo::class");
|
|||
|
} else if (IsRootObject(oPtr)) {
|
|||
|
Tcl_Panic("deleting class structure for non-deleted %s",
|
|||
|
"::oo::object");
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Squelch method implementation chain caches.
|
|||
|
*/
|
|||
|
|
|||
|
if (clsPtr->constructorChainPtr) {
|
|||
|
TclOODeleteChain(clsPtr->constructorChainPtr);
|
|||
|
clsPtr->constructorChainPtr = NULL;
|
|||
|
}
|
|||
|
if (clsPtr->destructorChainPtr) {
|
|||
|
TclOODeleteChain(clsPtr->destructorChainPtr);
|
|||
|
clsPtr->destructorChainPtr = NULL;
|
|||
|
}
|
|||
|
if (clsPtr->classChainCache) {
|
|||
|
CallChain *callPtr;
|
|||
|
|
|||
|
FOREACH_HASH_VALUE(callPtr, clsPtr->classChainCache) {
|
|||
|
TclOODeleteChain(callPtr);
|
|||
|
}
|
|||
|
Tcl_DeleteHashTable(clsPtr->classChainCache);
|
|||
|
ckfree(clsPtr->classChainCache);
|
|||
|
clsPtr->classChainCache = NULL;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Squelch our filter list.
|
|||
|
*/
|
|||
|
|
|||
|
if (clsPtr->filters.num) {
|
|||
|
Tcl_Obj *filterObj;
|
|||
|
|
|||
|
FOREACH(filterObj, clsPtr->filters) {
|
|||
|
TclDecrRefCount(filterObj);
|
|||
|
}
|
|||
|
ckfree(clsPtr->filters.list);
|
|||
|
clsPtr->filters.list = NULL;
|
|||
|
clsPtr->filters.num = 0;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Squelch our metadata.
|
|||
|
*/
|
|||
|
|
|||
|
if (clsPtr->metadataPtr != NULL) {
|
|||
|
Tcl_ObjectMetadataType *metadataTypePtr;
|
|||
|
ClientData value;
|
|||
|
|
|||
|
FOREACH_HASH(metadataTypePtr, value, clsPtr->metadataPtr) {
|
|||
|
metadataTypePtr->deleteProc(value);
|
|||
|
}
|
|||
|
Tcl_DeleteHashTable(clsPtr->metadataPtr);
|
|||
|
ckfree(clsPtr->metadataPtr);
|
|||
|
clsPtr->metadataPtr = NULL;
|
|||
|
}
|
|||
|
|
|||
|
if (clsPtr->mixins.num) {
|
|||
|
FOREACH(tmpClsPtr, clsPtr->mixins) {
|
|||
|
TclOORemoveFromMixinSubs(clsPtr, tmpClsPtr);
|
|||
|
TclOODecrRefCount(tmpClsPtr->thisPtr);
|
|||
|
}
|
|||
|
ckfree(clsPtr->mixins.list);
|
|||
|
clsPtr->mixins.list = NULL;
|
|||
|
clsPtr->mixins.num = 0;
|
|||
|
}
|
|||
|
|
|||
|
if (clsPtr->superclasses.num > 0) {
|
|||
|
FOREACH(tmpClsPtr, clsPtr->superclasses) {
|
|||
|
TclOORemoveFromSubclasses(clsPtr, tmpClsPtr);
|
|||
|
TclOODecrRefCount(tmpClsPtr->thisPtr);
|
|||
|
}
|
|||
|
ckfree(clsPtr->superclasses.list);
|
|||
|
clsPtr->superclasses.num = 0;
|
|||
|
clsPtr->superclasses.list = NULL;
|
|||
|
}
|
|||
|
|
|||
|
FOREACH_HASH_VALUE(mPtr, &clsPtr->classMethods) {
|
|||
|
TclOODelMethodRef(mPtr);
|
|||
|
}
|
|||
|
Tcl_DeleteHashTable(&clsPtr->classMethods);
|
|||
|
TclOODelMethodRef(clsPtr->constructorPtr);
|
|||
|
TclOODelMethodRef(clsPtr->destructorPtr);
|
|||
|
|
|||
|
FOREACH(variableObj, clsPtr->variables) {
|
|||
|
TclDecrRefCount(variableObj);
|
|||
|
}
|
|||
|
if (i) {
|
|||
|
ckfree(clsPtr->variables.list);
|
|||
|
}
|
|||
|
|
|||
|
if (IsRootClass(oPtr) && !Destructing(fPtr->objectCls->thisPtr)) {
|
|||
|
Tcl_DeleteCommandFromToken(interp, fPtr->objectCls->thisPtr->command);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* ----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* ObjectNamespaceDeleted --
|
|||
|
*
|
|||
|
* Callback when the object's namespace is deleted. Used to clean up the
|
|||
|
* data structures associated with the object. The complicated bit is
|
|||
|
* that this can sometimes happen before the object's command is deleted
|
|||
|
* (interpreter teardown is complex!)
|
|||
|
*
|
|||
|
* ----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
static void
|
|||
|
ObjectNamespaceDeleted(
|
|||
|
ClientData clientData) /* Pointer to the class whose namespace is
|
|||
|
* being deleted. */
|
|||
|
{
|
|||
|
Object *oPtr = clientData;
|
|||
|
Foundation *fPtr = oPtr->fPtr;
|
|||
|
FOREACH_HASH_DECLS;
|
|||
|
Class *mixinPtr;
|
|||
|
Method *mPtr;
|
|||
|
Tcl_Obj *filterObj, *variableObj;
|
|||
|
Tcl_Interp *interp = oPtr->fPtr->interp;
|
|||
|
int i;
|
|||
|
|
|||
|
if (Destructing(oPtr)) {
|
|||
|
/*
|
|||
|
* TODO: Can ObjectNamespaceDeleted ever be called twice? If not,
|
|||
|
* this guard could be removed.
|
|||
|
*/
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* One rule for the teardown routines is that if an object is in the
|
|||
|
* process of being deleted, nothing else may modify its bookeeping
|
|||
|
* records. This is the flag that
|
|||
|
*/
|
|||
|
oPtr->flags |= OBJECT_DESTRUCTING;
|
|||
|
|
|||
|
/*
|
|||
|
* Let the dominoes fall!
|
|||
|
*/
|
|||
|
|
|||
|
if (oPtr->classPtr) {
|
|||
|
TclOODeleteDescendants(interp, oPtr);
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* We do not run destructors on the core class objects when the
|
|||
|
* interpreter is being deleted; their incestuous nature causes problems
|
|||
|
* in that case when the destructor is partially deleted before the uses
|
|||
|
* of it have gone. [Bug 2949397]
|
|||
|
*/
|
|||
|
|
|||
|
if (!Tcl_InterpDeleted(interp) && !(oPtr->flags & DESTRUCTOR_CALLED)) {
|
|||
|
CallContext *contextPtr =
|
|||
|
TclOOGetCallContext(oPtr, NULL, DESTRUCTOR, NULL);
|
|||
|
int result;
|
|||
|
Tcl_InterpState state;
|
|||
|
|
|||
|
oPtr->flags |= DESTRUCTOR_CALLED;
|
|||
|
|
|||
|
if (contextPtr != NULL) {
|
|||
|
contextPtr->callPtr->flags |= DESTRUCTOR;
|
|||
|
contextPtr->skip = 0;
|
|||
|
state = Tcl_SaveInterpState(interp, TCL_OK);
|
|||
|
result = Tcl_NRCallObjProc(interp, TclOOInvokeContext,
|
|||
|
contextPtr, 0, NULL);
|
|||
|
if (result != TCL_OK) {
|
|||
|
Tcl_BackgroundException(interp, result);
|
|||
|
}
|
|||
|
Tcl_RestoreInterpState(interp, state);
|
|||
|
TclOODeleteContext(contextPtr);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Instruct everyone to no longer use any allocated fields of the object.
|
|||
|
* Also delete the command that refers to the object at this point (if it
|
|||
|
* still exists) because otherwise its pointer to the object points into
|
|||
|
* freed memory.
|
|||
|
*/
|
|||
|
|
|||
|
if (((Command *) oPtr->command)->flags && CMD_IS_DELETED) {
|
|||
|
/*
|
|||
|
* Something has already started the command deletion process. We can
|
|||
|
* go ahead and clean up the the namespace,
|
|||
|
*/
|
|||
|
} else {
|
|||
|
/*
|
|||
|
* The namespace must have been deleted directly. Delete the command
|
|||
|
* as well.
|
|||
|
*/
|
|||
|
|
|||
|
Tcl_DeleteCommandFromToken(oPtr->fPtr->interp, oPtr->command);
|
|||
|
}
|
|||
|
|
|||
|
if (oPtr->myCommand) {
|
|||
|
Tcl_DeleteCommandFromToken(oPtr->fPtr->interp, oPtr->myCommand);
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Splice the object out of its context. After this, we must *not* call
|
|||
|
* methods on the object.
|
|||
|
*/
|
|||
|
|
|||
|
/* TODO: Should this be protected with a !IsRoot() condition? */
|
|||
|
TclOORemoveFromInstances(oPtr, oPtr->selfCls);
|
|||
|
|
|||
|
if (oPtr->mixins.num > 0) {
|
|||
|
FOREACH(mixinPtr, oPtr->mixins) {
|
|||
|
TclOORemoveFromInstances(oPtr, mixinPtr);
|
|||
|
TclOODecrRefCount(mixinPtr->thisPtr);
|
|||
|
}
|
|||
|
if (oPtr->mixins.list != NULL) {
|
|||
|
ckfree(oPtr->mixins.list);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
FOREACH(filterObj, oPtr->filters) {
|
|||
|
TclDecrRefCount(filterObj);
|
|||
|
}
|
|||
|
if (i) {
|
|||
|
ckfree(oPtr->filters.list);
|
|||
|
}
|
|||
|
|
|||
|
if (oPtr->methodsPtr) {
|
|||
|
FOREACH_HASH_VALUE(mPtr, oPtr->methodsPtr) {
|
|||
|
TclOODelMethodRef(mPtr);
|
|||
|
}
|
|||
|
Tcl_DeleteHashTable(oPtr->methodsPtr);
|
|||
|
ckfree(oPtr->methodsPtr);
|
|||
|
}
|
|||
|
|
|||
|
FOREACH(variableObj, oPtr->variables) {
|
|||
|
TclDecrRefCount(variableObj);
|
|||
|
}
|
|||
|
if (i) {
|
|||
|
ckfree(oPtr->variables.list);
|
|||
|
}
|
|||
|
|
|||
|
if (oPtr->chainCache) {
|
|||
|
TclOODeleteChainCache(oPtr->chainCache);
|
|||
|
}
|
|||
|
|
|||
|
SquelchCachedName(oPtr);
|
|||
|
|
|||
|
if (oPtr->metadataPtr != NULL) {
|
|||
|
Tcl_ObjectMetadataType *metadataTypePtr;
|
|||
|
ClientData value;
|
|||
|
|
|||
|
FOREACH_HASH(metadataTypePtr, value, oPtr->metadataPtr) {
|
|||
|
metadataTypePtr->deleteProc(value);
|
|||
|
}
|
|||
|
Tcl_DeleteHashTable(oPtr->metadataPtr);
|
|||
|
ckfree(oPtr->metadataPtr);
|
|||
|
oPtr->metadataPtr = NULL;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Because an object can be a class that is an instance of itself, the
|
|||
|
* class object's class structure should only be cleaned after most of
|
|||
|
* the cleanup on the object is done.
|
|||
|
*
|
|||
|
* The class of objects needs some special care; if it is deleted (and
|
|||
|
* we're not killing the whole interpreter) we force the delete of the
|
|||
|
* class of classes now as well. Due to the incestuous nature of those two
|
|||
|
* classes, if one goes the other must too and yet the tangle can
|
|||
|
* sometimes not go away automatically; we force it here. [Bug 2962664]
|
|||
|
*/
|
|||
|
|
|||
|
if (IsRootObject(oPtr) && !Destructing(fPtr->classCls->thisPtr)
|
|||
|
&& !Tcl_InterpDeleted(interp)) {
|
|||
|
|
|||
|
Tcl_DeleteCommandFromToken(interp, fPtr->classCls->thisPtr->command);
|
|||
|
}
|
|||
|
|
|||
|
if (oPtr->classPtr != NULL) {
|
|||
|
TclOOReleaseClassContents(interp, oPtr);
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Delete the object structure itself.
|
|||
|
*/
|
|||
|
|
|||
|
TclNsDecrRefCount((Namespace *)oPtr->namespacePtr);
|
|||
|
oPtr->namespacePtr = NULL;
|
|||
|
TclOODecrRefCount(oPtr->selfCls->thisPtr);
|
|||
|
oPtr->selfCls = NULL;
|
|||
|
TclOODecrRefCount(oPtr);
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* ----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* TclOODecrRef --
|
|||
|
*
|
|||
|
* Decrement the refcount of an object and deallocate storage then object
|
|||
|
* is no longer referenced. Returns 1 if storage was deallocated, and 0
|
|||
|
* otherwise.
|
|||
|
*
|
|||
|
* ----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
int TclOODecrRefCount(Object *oPtr) {
|
|||
|
if (oPtr->refCount-- <= 1) {
|
|||
|
if (oPtr->classPtr != NULL) {
|
|||
|
ckfree(oPtr->classPtr);
|
|||
|
}
|
|||
|
ckfree(oPtr);
|
|||
|
return 1;
|
|||
|
}
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* ----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* TclOOObjectDestroyed --
|
|||
|
*
|
|||
|
* Returns TCL_OK if an object is entirely deleted, i.e. the destruction
|
|||
|
* sequence has completed.
|
|||
|
*
|
|||
|
* ----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
int TclOOObjectDestroyed(Object *oPtr) {
|
|||
|
return (oPtr->namespacePtr == NULL);
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Setting the "empty" location to NULL makes debugging a little easier.
|
|||
|
*/
|
|||
|
|
|||
|
#define REMOVEBODY { \
|
|||
|
for (; idx < num - 1; idx++) { \
|
|||
|
list[idx] = list[idx + 1]; \
|
|||
|
} \
|
|||
|
list[idx] = NULL; \
|
|||
|
return; \
|
|||
|
}
|
|||
|
void RemoveClass(Class **list, int num, int idx) REMOVEBODY
|
|||
|
|
|||
|
void RemoveObject(Object **list, int num, int idx) REMOVEBODY
|
|||
|
|
|||
|
/*
|
|||
|
* ----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* TclOORemoveFromInstances --
|
|||
|
*
|
|||
|
* Utility function to remove an object from the list of instances within
|
|||
|
* a class.
|
|||
|
*
|
|||
|
* ----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
int
|
|||
|
TclOORemoveFromInstances(
|
|||
|
Object *oPtr, /* The instance to remove. */
|
|||
|
Class *clsPtr) /* The class (possibly) containing the
|
|||
|
* reference to the instance. */
|
|||
|
{
|
|||
|
int i, res = 0;
|
|||
|
Object *instPtr;
|
|||
|
|
|||
|
FOREACH(instPtr, clsPtr->instances) {
|
|||
|
if (oPtr == instPtr) {
|
|||
|
RemoveItem(Object, clsPtr->instances, i);
|
|||
|
TclOODecrRefCount(oPtr);
|
|||
|
res++;
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
return res;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* ----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* TclOOAddToInstances --
|
|||
|
*
|
|||
|
* Utility function to add an object to the list of instances within a
|
|||
|
* class.
|
|||
|
*
|
|||
|
* ----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
void
|
|||
|
TclOOAddToInstances(
|
|||
|
Object *oPtr, /* The instance to add. */
|
|||
|
Class *clsPtr) /* The class to add the instance to. It is
|
|||
|
* assumed that the class is not already
|
|||
|
* present as an instance in the class. */
|
|||
|
{
|
|||
|
if (clsPtr->instances.num >= clsPtr->instances.size) {
|
|||
|
clsPtr->instances.size += ALLOC_CHUNK;
|
|||
|
if (clsPtr->instances.size == ALLOC_CHUNK) {
|
|||
|
clsPtr->instances.list = ckalloc(sizeof(Object *) * ALLOC_CHUNK);
|
|||
|
} else {
|
|||
|
clsPtr->instances.list = ckrealloc(clsPtr->instances.list,
|
|||
|
sizeof(Object *) * clsPtr->instances.size);
|
|||
|
}
|
|||
|
}
|
|||
|
clsPtr->instances.list[clsPtr->instances.num++] = oPtr;
|
|||
|
AddRef(oPtr);
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* ----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* TclOORemoveFromMixins --
|
|||
|
*
|
|||
|
* Utility function to remove a class from the list of mixins within an
|
|||
|
* object.
|
|||
|
*
|
|||
|
* ----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
int
|
|||
|
TclOORemoveFromMixins(
|
|||
|
Class *mixinPtr, /* The mixin to remove. */
|
|||
|
Object *oPtr) /* The object (possibly) containing the
|
|||
|
* reference to the mixin. */
|
|||
|
{
|
|||
|
int i, res = 0;
|
|||
|
Class *mixPtr;
|
|||
|
|
|||
|
FOREACH(mixPtr, oPtr->mixins) {
|
|||
|
if (mixinPtr == mixPtr) {
|
|||
|
RemoveItem(Class, oPtr->mixins, i);
|
|||
|
TclOODecrRefCount(mixPtr->thisPtr);
|
|||
|
res++;
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
if (oPtr->mixins.num == 0) {
|
|||
|
ckfree(oPtr->mixins.list);
|
|||
|
oPtr->mixins.list = NULL;
|
|||
|
}
|
|||
|
return res;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* ----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* TclOORemoveFromSubclasses --
|
|||
|
*
|
|||
|
* Utility function to remove a class from the list of subclasses within
|
|||
|
* another class. Returns the number of removals performed.
|
|||
|
*
|
|||
|
* ----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
int
|
|||
|
TclOORemoveFromSubclasses(
|
|||
|
Class *subPtr, /* The subclass to remove. */
|
|||
|
Class *superPtr) /* The superclass to possibly remove the
|
|||
|
* subclass reference from. */
|
|||
|
{
|
|||
|
int i, res = 0;
|
|||
|
Class *subclsPtr;
|
|||
|
|
|||
|
FOREACH(subclsPtr, superPtr->subclasses) {
|
|||
|
if (subPtr == subclsPtr) {
|
|||
|
RemoveItem(Class, superPtr->subclasses, i);
|
|||
|
TclOODecrRefCount(subPtr->thisPtr);
|
|||
|
res++;
|
|||
|
}
|
|||
|
}
|
|||
|
return res;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* ----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* TclOOAddToSubclasses --
|
|||
|
*
|
|||
|
* Utility function to add a class to the list of subclasses within
|
|||
|
* another class.
|
|||
|
*
|
|||
|
* ----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
void
|
|||
|
TclOOAddToSubclasses(
|
|||
|
Class *subPtr, /* The subclass to add. */
|
|||
|
Class *superPtr) /* The superclass to add the subclass to. It
|
|||
|
* is assumed that the class is not already
|
|||
|
* present as a subclass in the superclass. */
|
|||
|
{
|
|||
|
if (Destructing(superPtr->thisPtr)) {
|
|||
|
return;
|
|||
|
}
|
|||
|
if (superPtr->subclasses.num >= superPtr->subclasses.size) {
|
|||
|
superPtr->subclasses.size += ALLOC_CHUNK;
|
|||
|
if (superPtr->subclasses.size == ALLOC_CHUNK) {
|
|||
|
superPtr->subclasses.list = ckalloc(sizeof(Class *) * ALLOC_CHUNK);
|
|||
|
} else {
|
|||
|
superPtr->subclasses.list = ckrealloc(superPtr->subclasses.list,
|
|||
|
sizeof(Class *) * superPtr->subclasses.size);
|
|||
|
}
|
|||
|
}
|
|||
|
superPtr->subclasses.list[superPtr->subclasses.num++] = subPtr;
|
|||
|
AddRef(subPtr->thisPtr);
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* ----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* TclOORemoveFromMixinSubs --
|
|||
|
*
|
|||
|
* Utility function to remove a class from the list of mixinSubs within
|
|||
|
* another class.
|
|||
|
*
|
|||
|
* ----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
int
|
|||
|
TclOORemoveFromMixinSubs(
|
|||
|
Class *subPtr, /* The subclass to remove. */
|
|||
|
Class *superPtr) /* The superclass to possibly remove the
|
|||
|
* subclass reference from. */
|
|||
|
{
|
|||
|
int i, res = 0;
|
|||
|
Class *subclsPtr;
|
|||
|
|
|||
|
FOREACH(subclsPtr, superPtr->mixinSubs) {
|
|||
|
if (subPtr == subclsPtr) {
|
|||
|
RemoveItem(Class, superPtr->mixinSubs, i);
|
|||
|
TclOODecrRefCount(subPtr->thisPtr);
|
|||
|
res++;
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
return res;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* ----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* TclOOAddToMixinSubs --
|
|||
|
*
|
|||
|
* Utility function to add a class to the list of mixinSubs within
|
|||
|
* another class.
|
|||
|
*
|
|||
|
* ----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
void
|
|||
|
TclOOAddToMixinSubs(
|
|||
|
Class *subPtr, /* The subclass to add. */
|
|||
|
Class *superPtr) /* The superclass to add the subclass to. It
|
|||
|
* is assumed that the class is not already
|
|||
|
* present as a subclass in the superclass. */
|
|||
|
{
|
|||
|
if (Destructing(superPtr->thisPtr)) {
|
|||
|
return;
|
|||
|
}
|
|||
|
if (superPtr->mixinSubs.num >= superPtr->mixinSubs.size) {
|
|||
|
superPtr->mixinSubs.size += ALLOC_CHUNK;
|
|||
|
if (superPtr->mixinSubs.size == ALLOC_CHUNK) {
|
|||
|
superPtr->mixinSubs.list = ckalloc(sizeof(Class *) * ALLOC_CHUNK);
|
|||
|
} else {
|
|||
|
superPtr->mixinSubs.list = ckrealloc(superPtr->mixinSubs.list,
|
|||
|
sizeof(Class *) * superPtr->mixinSubs.size);
|
|||
|
}
|
|||
|
}
|
|||
|
superPtr->mixinSubs.list[superPtr->mixinSubs.num++] = subPtr;
|
|||
|
AddRef(subPtr->thisPtr);
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* ----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* TclOOAllocClass --
|
|||
|
*
|
|||
|
* Allocate a basic class. Does not add class to its class's instance
|
|||
|
* list.
|
|||
|
*
|
|||
|
* ----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
Class *
|
|||
|
TclOOAllocClass(
|
|||
|
Tcl_Interp *interp, /* Interpreter within which to allocate the
|
|||
|
* class. */
|
|||
|
Object *useThisObj) /* Object that is to act as the class
|
|||
|
* representation. */
|
|||
|
{
|
|||
|
Foundation *fPtr = GetFoundation(interp);
|
|||
|
Class *clsPtr = ckalloc(sizeof(Class));
|
|||
|
|
|||
|
memset(clsPtr, 0, sizeof(Class));
|
|||
|
clsPtr->thisPtr = useThisObj;
|
|||
|
|
|||
|
/*
|
|||
|
* Configure the namespace path for the class's object.
|
|||
|
*/
|
|||
|
initClassPath(interp, clsPtr);
|
|||
|
|
|||
|
/*
|
|||
|
* Classes are subclasses of oo::object, i.e. the objects they create are
|
|||
|
* objects.
|
|||
|
*/
|
|||
|
|
|||
|
clsPtr->superclasses.num = 1;
|
|||
|
clsPtr->superclasses.list = ckalloc(sizeof(Class *));
|
|||
|
clsPtr->superclasses.list[0] = fPtr->objectCls;
|
|||
|
AddRef(fPtr->objectCls->thisPtr);
|
|||
|
|
|||
|
/*
|
|||
|
* Finish connecting the class structure to the object structure.
|
|||
|
*/
|
|||
|
|
|||
|
clsPtr->thisPtr->classPtr = clsPtr;
|
|||
|
|
|||
|
/*
|
|||
|
* That's the complicated bit. Now fill in the rest of the non-zero/NULL
|
|||
|
* fields.
|
|||
|
*/
|
|||
|
|
|||
|
Tcl_InitObjHashTable(&clsPtr->classMethods);
|
|||
|
return clsPtr;
|
|||
|
}
|
|||
|
static void
|
|||
|
initClassPath(Tcl_Interp *interp, Class *clsPtr) {
|
|||
|
Foundation *fPtr = GetFoundation(interp);
|
|||
|
if (fPtr->helpersNs != NULL) {
|
|||
|
Tcl_Namespace *path[2];
|
|||
|
path[0] = fPtr->helpersNs;
|
|||
|
path[1] = fPtr->ooNs;
|
|||
|
TclSetNsPath((Namespace *) clsPtr->thisPtr->namespacePtr, 2, path);
|
|||
|
} else {
|
|||
|
TclSetNsPath((Namespace *) clsPtr->thisPtr->namespacePtr, 1,
|
|||
|
&fPtr->ooNs);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* ----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* Tcl_NewObjectInstance --
|
|||
|
*
|
|||
|
* Allocate a new instance of an object.
|
|||
|
*
|
|||
|
* ----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
Tcl_Object
|
|||
|
Tcl_NewObjectInstance(
|
|||
|
Tcl_Interp *interp, /* Interpreter context. */
|
|||
|
Tcl_Class cls, /* Class to create an instance of. */
|
|||
|
const char *nameStr, /* Name of object to create, or NULL to ask
|
|||
|
* the code to pick its own unique name. */
|
|||
|
const char *nsNameStr, /* Name of namespace to create inside object,
|
|||
|
* or NULL to ask the code to pick its own
|
|||
|
* unique name. */
|
|||
|
int objc, /* Number of arguments. Negative value means
|
|||
|
* do not call constructor. */
|
|||
|
Tcl_Obj *const *objv, /* Argument list. */
|
|||
|
int skip) /* Number of arguments to _not_ pass to the
|
|||
|
* constructor. */
|
|||
|
{
|
|||
|
Class *classPtr = (Class *) cls;
|
|||
|
Object *oPtr;
|
|||
|
ClientData clientData[4];
|
|||
|
|
|||
|
oPtr = TclNewObjectInstanceCommon(interp, classPtr, nameStr, nsNameStr);
|
|||
|
if (oPtr == NULL) {return NULL;}
|
|||
|
|
|||
|
/*
|
|||
|
* Run constructors, except when objc < 0, which is a special flag case
|
|||
|
* used for object cloning only.
|
|||
|
*/
|
|||
|
|
|||
|
if (objc >= 0) {
|
|||
|
CallContext *contextPtr =
|
|||
|
TclOOGetCallContext(oPtr, NULL, CONSTRUCTOR, NULL);
|
|||
|
|
|||
|
if (contextPtr != NULL) {
|
|||
|
int isRoot, result;
|
|||
|
Tcl_InterpState state;
|
|||
|
|
|||
|
state = Tcl_SaveInterpState(interp, TCL_OK);
|
|||
|
contextPtr->callPtr->flags |= CONSTRUCTOR;
|
|||
|
contextPtr->skip = skip;
|
|||
|
|
|||
|
/*
|
|||
|
* Adjust the ensemble tracking record if necessary. [Bug 3514761]
|
|||
|
*/
|
|||
|
|
|||
|
isRoot = TclInitRewriteEnsemble(interp, skip, skip, objv);
|
|||
|
result = Tcl_NRCallObjProc(interp, TclOOInvokeContext, contextPtr,
|
|||
|
objc, objv);
|
|||
|
|
|||
|
if (isRoot) {
|
|||
|
TclResetRewriteEnsemble(interp, 1);
|
|||
|
}
|
|||
|
|
|||
|
clientData[0] = contextPtr;
|
|||
|
clientData[1] = oPtr;
|
|||
|
clientData[2] = state;
|
|||
|
clientData[3] = &oPtr;
|
|||
|
|
|||
|
result = FinalizeAlloc(clientData, interp, result);
|
|||
|
if (result != TCL_OK) {
|
|||
|
return NULL;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return (Tcl_Object) oPtr;
|
|||
|
}
|
|||
|
|
|||
|
int
|
|||
|
TclNRNewObjectInstance(
|
|||
|
Tcl_Interp *interp, /* Interpreter context. */
|
|||
|
Tcl_Class cls, /* Class to create an instance of. */
|
|||
|
const char *nameStr, /* Name of object to create, or NULL to ask
|
|||
|
* the code to pick its own unique name. */
|
|||
|
const char *nsNameStr, /* Name of namespace to create inside object,
|
|||
|
* or NULL to ask the code to pick its own
|
|||
|
* unique name. */
|
|||
|
int objc, /* Number of arguments. Negative value means
|
|||
|
* do not call constructor. */
|
|||
|
Tcl_Obj *const *objv, /* Argument list. */
|
|||
|
int skip, /* Number of arguments to _not_ pass to the
|
|||
|
* constructor. */
|
|||
|
Tcl_Object *objectPtr) /* Place to write the object reference upon
|
|||
|
* successful allocation. */
|
|||
|
{
|
|||
|
Class *classPtr = (Class *) cls;
|
|||
|
CallContext *contextPtr;
|
|||
|
Tcl_InterpState state;
|
|||
|
Object *oPtr;
|
|||
|
|
|||
|
oPtr = TclNewObjectInstanceCommon(interp, classPtr, nameStr, nsNameStr);
|
|||
|
if (oPtr == NULL) {return TCL_ERROR;}
|
|||
|
|
|||
|
/*
|
|||
|
* Run constructors, except when objc < 0 (a special flag case used for
|
|||
|
* object cloning only). If there aren't any constructors, we do nothing.
|
|||
|
*/
|
|||
|
|
|||
|
if (objc < 0) {
|
|||
|
*objectPtr = (Tcl_Object) oPtr;
|
|||
|
return TCL_OK;
|
|||
|
}
|
|||
|
contextPtr = TclOOGetCallContext(oPtr, NULL, CONSTRUCTOR, NULL);
|
|||
|
if (contextPtr == NULL) {
|
|||
|
*objectPtr = (Tcl_Object) oPtr;
|
|||
|
return TCL_OK;
|
|||
|
}
|
|||
|
|
|||
|
state = Tcl_SaveInterpState(interp, TCL_OK);
|
|||
|
contextPtr->callPtr->flags |= CONSTRUCTOR;
|
|||
|
contextPtr->skip = skip;
|
|||
|
|
|||
|
/*
|
|||
|
* Adjust the ensemble tracking record if necessary. [Bug 3514761]
|
|||
|
*/
|
|||
|
|
|||
|
if (TclInitRewriteEnsemble(interp, skip, skip, objv)) {
|
|||
|
TclNRAddCallback(interp, TclClearRootEnsemble, NULL, NULL, NULL, NULL);
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Fire off the constructors non-recursively.
|
|||
|
*/
|
|||
|
|
|||
|
TclNRAddCallback(interp, FinalizeAlloc, contextPtr, oPtr, state,
|
|||
|
objectPtr);
|
|||
|
TclPushTailcallPoint(interp);
|
|||
|
return TclOOInvokeContext(contextPtr, interp, objc, objv);
|
|||
|
}
|
|||
|
|
|||
|
Object *
|
|||
|
TclNewObjectInstanceCommon(
|
|||
|
Tcl_Interp *interp,
|
|||
|
Class *classPtr,
|
|||
|
const char *nameStr,
|
|||
|
const char *nsNameStr)
|
|||
|
{
|
|||
|
Tcl_HashEntry *hPtr;
|
|||
|
Foundation *fPtr = GetFoundation(interp);
|
|||
|
Object *oPtr;
|
|||
|
const char *simpleName = NULL;
|
|||
|
Namespace *nsPtr = NULL, *dummy,
|
|||
|
*inNsPtr = (Namespace *)TclGetCurrentNamespace(interp);
|
|||
|
|
|||
|
if (nameStr) {
|
|||
|
TclGetNamespaceForQualName(interp, nameStr, inNsPtr,
|
|||
|
TCL_CREATE_NS_IF_UNKNOWN, &nsPtr, &dummy, &dummy, &simpleName);
|
|||
|
|
|||
|
/*
|
|||
|
* Disallow creation of an object over an existing command.
|
|||
|
*/
|
|||
|
|
|||
|
hPtr = Tcl_FindHashEntry(&nsPtr->cmdTable, simpleName);
|
|||
|
if (hPtr) {
|
|||
|
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
|
|||
|
"can't create object \"%s\": command already exists with"
|
|||
|
" that name", nameStr));
|
|||
|
Tcl_SetErrorCode(interp, "TCL", "OO", "OVERWRITE_OBJECT", NULL);
|
|||
|
return NULL;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Create the object.
|
|||
|
*/
|
|||
|
|
|||
|
oPtr = AllocObject(interp, simpleName, nsPtr, nsNameStr);
|
|||
|
oPtr->selfCls = classPtr;
|
|||
|
AddRef(classPtr->thisPtr);
|
|||
|
TclOOAddToInstances(oPtr, classPtr);
|
|||
|
|
|||
|
/*
|
|||
|
* Check to see if we're really creating a class. If so, allocate the
|
|||
|
* class structure as well.
|
|||
|
*/
|
|||
|
|
|||
|
if (TclOOIsReachable(fPtr->classCls, classPtr)) {
|
|||
|
/*
|
|||
|
* Is a class, so attach a class structure. Note that the
|
|||
|
* TclOOAllocClass function splices the structure into the object, so
|
|||
|
* we don't have to. Once that's done, we need to repatch the object
|
|||
|
* to have the right class since TclOOAllocClass interferes with that.
|
|||
|
*/
|
|||
|
|
|||
|
TclOOAllocClass(interp, oPtr);
|
|||
|
TclOOAddToSubclasses(oPtr->classPtr, fPtr->objectCls);
|
|||
|
} else {
|
|||
|
oPtr->classPtr = NULL;
|
|||
|
}
|
|||
|
return oPtr;
|
|||
|
}
|
|||
|
|
|||
|
static int
|
|||
|
FinalizeAlloc(
|
|||
|
ClientData data[],
|
|||
|
Tcl_Interp *interp,
|
|||
|
int result)
|
|||
|
{
|
|||
|
CallContext *contextPtr = data[0];
|
|||
|
Object *oPtr = data[1];
|
|||
|
Tcl_InterpState state = data[2];
|
|||
|
Tcl_Object *objectPtr = data[3];
|
|||
|
|
|||
|
/*
|
|||
|
* Ensure an error if the object was deleted in the constructor.
|
|||
|
* Don't want to lose errors by accident. [Bug 2903011]
|
|||
|
*/
|
|||
|
|
|||
|
if (result != TCL_ERROR && Destructing(oPtr)) {
|
|||
|
Tcl_SetObjResult(interp, Tcl_NewStringObj(
|
|||
|
"object deleted in constructor", -1));
|
|||
|
Tcl_SetErrorCode(interp, "TCL", "OO", "STILLBORN", NULL);
|
|||
|
result = TCL_ERROR;
|
|||
|
}
|
|||
|
if (result != TCL_OK) {
|
|||
|
Tcl_DiscardInterpState(state);
|
|||
|
|
|||
|
/*
|
|||
|
* Take care to not delete a deleted object; that would be bad. [Bug
|
|||
|
* 2903011] Also take care to make sure that we have the name of the
|
|||
|
* command before we delete it. [Bug 9dd1bd7a74]
|
|||
|
*/
|
|||
|
|
|||
|
if (!Destructing(oPtr)) {
|
|||
|
(void) TclOOObjectName(interp, oPtr);
|
|||
|
Tcl_DeleteCommandFromToken(interp, oPtr->command);
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* This decrements the refcount of oPtr.
|
|||
|
*/
|
|||
|
|
|||
|
TclOODeleteContext(contextPtr);
|
|||
|
return TCL_ERROR;
|
|||
|
}
|
|||
|
Tcl_RestoreInterpState(interp, state);
|
|||
|
*objectPtr = (Tcl_Object) oPtr;
|
|||
|
|
|||
|
/*
|
|||
|
* This decrements the refcount of oPtr.
|
|||
|
*/
|
|||
|
|
|||
|
TclOODeleteContext(contextPtr);
|
|||
|
return TCL_OK;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* ----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* Tcl_CopyObjectInstance --
|
|||
|
*
|
|||
|
* Creates a copy of an object. Does not copy the backing namespace,
|
|||
|
* since the correct way to do that (e.g., shallow/deep) depends on the
|
|||
|
* object/class's own policies.
|
|||
|
*
|
|||
|
* ----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
Tcl_Object
|
|||
|
Tcl_CopyObjectInstance(
|
|||
|
Tcl_Interp *interp,
|
|||
|
Tcl_Object sourceObject,
|
|||
|
const char *targetName,
|
|||
|
const char *targetNamespaceName)
|
|||
|
{
|
|||
|
Object *oPtr = (Object *) sourceObject, *o2Ptr;
|
|||
|
FOREACH_HASH_DECLS;
|
|||
|
Method *mPtr;
|
|||
|
Class *mixinPtr;
|
|||
|
CallContext *contextPtr;
|
|||
|
Tcl_Obj *keyPtr, *filterObj, *variableObj, *args[3];
|
|||
|
int i, result;
|
|||
|
|
|||
|
/*
|
|||
|
* Sanity check.
|
|||
|
*/
|
|||
|
|
|||
|
if (IsRootClass(oPtr)) {
|
|||
|
Tcl_SetObjResult(interp, Tcl_NewStringObj(
|
|||
|
"may not clone the class of classes", -1));
|
|||
|
Tcl_SetErrorCode(interp, "TCL", "OO", "CLONING_CLASS", NULL);
|
|||
|
return NULL;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Build the instance. Note that this does not run any constructors.
|
|||
|
*/
|
|||
|
|
|||
|
o2Ptr = (Object *) Tcl_NewObjectInstance(interp,
|
|||
|
(Tcl_Class) oPtr->selfCls, targetName, targetNamespaceName, -1,
|
|||
|
NULL, -1);
|
|||
|
if (o2Ptr == NULL) {
|
|||
|
return NULL;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Copy the object-local methods to the new object.
|
|||
|
*/
|
|||
|
|
|||
|
if (oPtr->methodsPtr) {
|
|||
|
FOREACH_HASH(keyPtr, mPtr, oPtr->methodsPtr) {
|
|||
|
if (CloneObjectMethod(interp, o2Ptr, mPtr, keyPtr) != TCL_OK) {
|
|||
|
Tcl_DeleteCommandFromToken(interp, o2Ptr->command);
|
|||
|
return NULL;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Copy the object's mixin references to the new object.
|
|||
|
*/
|
|||
|
|
|||
|
if (o2Ptr->mixins.num != 0) {
|
|||
|
FOREACH(mixinPtr, o2Ptr->mixins) {
|
|||
|
if (mixinPtr && mixinPtr != o2Ptr->selfCls) {
|
|||
|
TclOORemoveFromInstances(o2Ptr, mixinPtr);
|
|||
|
}
|
|||
|
TclOODecrRefCount(mixinPtr->thisPtr);
|
|||
|
}
|
|||
|
ckfree(o2Ptr->mixins.list);
|
|||
|
}
|
|||
|
DUPLICATE(o2Ptr->mixins, oPtr->mixins, Class *);
|
|||
|
FOREACH(mixinPtr, o2Ptr->mixins) {
|
|||
|
if (mixinPtr && mixinPtr != o2Ptr->selfCls) {
|
|||
|
TclOOAddToInstances(o2Ptr, mixinPtr);
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* For the reference just created in DUPLICATE.
|
|||
|
*/
|
|||
|
|
|||
|
AddRef(mixinPtr->thisPtr);
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Copy the object's filter list to the new object.
|
|||
|
*/
|
|||
|
|
|||
|
DUPLICATE(o2Ptr->filters, oPtr->filters, Tcl_Obj *);
|
|||
|
FOREACH(filterObj, o2Ptr->filters) {
|
|||
|
Tcl_IncrRefCount(filterObj);
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Copy the object's variable resolution list to the new object.
|
|||
|
*/
|
|||
|
|
|||
|
DUPLICATE(o2Ptr->variables, oPtr->variables, Tcl_Obj *);
|
|||
|
FOREACH(variableObj, o2Ptr->variables) {
|
|||
|
Tcl_IncrRefCount(variableObj);
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Copy the object's flags to the new object, clearing those that must be
|
|||
|
* kept object-local. The duplicate is never deleted at this point, nor is
|
|||
|
* it the root of the object system or in the midst of processing a filter
|
|||
|
* call.
|
|||
|
*/
|
|||
|
|
|||
|
o2Ptr->flags = oPtr->flags & ~(
|
|||
|
OBJECT_DESTRUCTING | ROOT_OBJECT | ROOT_CLASS | FILTER_HANDLING);
|
|||
|
|
|||
|
/*
|
|||
|
* Copy the object's metadata.
|
|||
|
*/
|
|||
|
|
|||
|
if (oPtr->metadataPtr != NULL) {
|
|||
|
Tcl_ObjectMetadataType *metadataTypePtr;
|
|||
|
ClientData value, duplicate;
|
|||
|
|
|||
|
FOREACH_HASH(metadataTypePtr, value, oPtr->metadataPtr) {
|
|||
|
if (metadataTypePtr->cloneProc == NULL) {
|
|||
|
duplicate = value;
|
|||
|
} else {
|
|||
|
if (metadataTypePtr->cloneProc(interp, value,
|
|||
|
&duplicate) != TCL_OK) {
|
|||
|
Tcl_DeleteCommandFromToken(interp, o2Ptr->command);
|
|||
|
return NULL;
|
|||
|
}
|
|||
|
}
|
|||
|
if (duplicate != NULL) {
|
|||
|
Tcl_ObjectSetMetadata((Tcl_Object) o2Ptr, metadataTypePtr,
|
|||
|
duplicate);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Copy the class, if present. Note that if there is a class present in
|
|||
|
* the source object, there must also be one in the copy.
|
|||
|
*/
|
|||
|
|
|||
|
if (oPtr->classPtr != NULL) {
|
|||
|
Class *clsPtr = oPtr->classPtr;
|
|||
|
Class *cls2Ptr = o2Ptr->classPtr;
|
|||
|
Class *superPtr;
|
|||
|
|
|||
|
/*
|
|||
|
* Copy the class flags across.
|
|||
|
*/
|
|||
|
|
|||
|
cls2Ptr->flags = clsPtr->flags;
|
|||
|
|
|||
|
/*
|
|||
|
* Ensure that the new class's superclass structure is the same as the
|
|||
|
* old class's.
|
|||
|
*/
|
|||
|
|
|||
|
FOREACH(superPtr, cls2Ptr->superclasses) {
|
|||
|
TclOORemoveFromSubclasses(cls2Ptr, superPtr);
|
|||
|
TclOODecrRefCount(superPtr->thisPtr);
|
|||
|
}
|
|||
|
if (cls2Ptr->superclasses.num) {
|
|||
|
cls2Ptr->superclasses.list = ckrealloc(cls2Ptr->superclasses.list,
|
|||
|
sizeof(Class *) * clsPtr->superclasses.num);
|
|||
|
} else {
|
|||
|
cls2Ptr->superclasses.list =
|
|||
|
ckalloc(sizeof(Class *) * clsPtr->superclasses.num);
|
|||
|
}
|
|||
|
memcpy(cls2Ptr->superclasses.list, clsPtr->superclasses.list,
|
|||
|
sizeof(Class *) * clsPtr->superclasses.num);
|
|||
|
cls2Ptr->superclasses.num = clsPtr->superclasses.num;
|
|||
|
FOREACH(superPtr, cls2Ptr->superclasses) {
|
|||
|
TclOOAddToSubclasses(cls2Ptr, superPtr);
|
|||
|
|
|||
|
/*
|
|||
|
* For the new item in cls2Ptr->superclasses that memcpy just
|
|||
|
* created.
|
|||
|
*/
|
|||
|
|
|||
|
AddRef(superPtr->thisPtr);
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Duplicate the source class's filters.
|
|||
|
*/
|
|||
|
|
|||
|
DUPLICATE(cls2Ptr->filters, clsPtr->filters, Tcl_Obj *);
|
|||
|
FOREACH(filterObj, cls2Ptr->filters) {
|
|||
|
Tcl_IncrRefCount(filterObj);
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Copy the source class's variable resolution list.
|
|||
|
*/
|
|||
|
|
|||
|
DUPLICATE(cls2Ptr->variables, clsPtr->variables, Tcl_Obj *);
|
|||
|
FOREACH(variableObj, cls2Ptr->variables) {
|
|||
|
Tcl_IncrRefCount(variableObj);
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Duplicate the source class's mixins (which cannot be circular
|
|||
|
* references to the duplicate).
|
|||
|
*/
|
|||
|
|
|||
|
if (cls2Ptr->mixins.num != 0) {
|
|||
|
FOREACH(mixinPtr, cls2Ptr->mixins) {
|
|||
|
TclOORemoveFromMixinSubs(cls2Ptr, mixinPtr);
|
|||
|
TclOODecrRefCount(mixinPtr->thisPtr);
|
|||
|
}
|
|||
|
ckfree(clsPtr->mixins.list);
|
|||
|
}
|
|||
|
DUPLICATE(cls2Ptr->mixins, clsPtr->mixins, Class *);
|
|||
|
FOREACH(mixinPtr, cls2Ptr->mixins) {
|
|||
|
TclOOAddToMixinSubs(cls2Ptr, mixinPtr);
|
|||
|
|
|||
|
/*
|
|||
|
* For the copy just created in DUPLICATE.
|
|||
|
*/
|
|||
|
|
|||
|
AddRef(mixinPtr->thisPtr);
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Duplicate the source class's methods, constructor and destructor.
|
|||
|
*/
|
|||
|
|
|||
|
FOREACH_HASH(keyPtr, mPtr, &clsPtr->classMethods) {
|
|||
|
if (CloneClassMethod(interp, cls2Ptr, mPtr, keyPtr,
|
|||
|
NULL) != TCL_OK) {
|
|||
|
Tcl_DeleteCommandFromToken(interp, o2Ptr->command);
|
|||
|
return NULL;
|
|||
|
}
|
|||
|
}
|
|||
|
if (clsPtr->constructorPtr) {
|
|||
|
if (CloneClassMethod(interp, cls2Ptr, clsPtr->constructorPtr,
|
|||
|
NULL, &cls2Ptr->constructorPtr) != TCL_OK) {
|
|||
|
Tcl_DeleteCommandFromToken(interp, o2Ptr->command);
|
|||
|
return NULL;
|
|||
|
}
|
|||
|
}
|
|||
|
if (clsPtr->destructorPtr) {
|
|||
|
if (CloneClassMethod(interp, cls2Ptr, clsPtr->destructorPtr, NULL,
|
|||
|
&cls2Ptr->destructorPtr) != TCL_OK) {
|
|||
|
Tcl_DeleteCommandFromToken(interp, o2Ptr->command);
|
|||
|
return NULL;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Duplicate the class's metadata.
|
|||
|
*/
|
|||
|
|
|||
|
if (clsPtr->metadataPtr != NULL) {
|
|||
|
Tcl_ObjectMetadataType *metadataTypePtr;
|
|||
|
ClientData value, duplicate;
|
|||
|
|
|||
|
FOREACH_HASH(metadataTypePtr, value, clsPtr->metadataPtr) {
|
|||
|
if (metadataTypePtr->cloneProc == NULL) {
|
|||
|
duplicate = value;
|
|||
|
} else {
|
|||
|
if (metadataTypePtr->cloneProc(interp, value,
|
|||
|
&duplicate) != TCL_OK) {
|
|||
|
Tcl_DeleteCommandFromToken(interp, o2Ptr->command);
|
|||
|
return NULL;
|
|||
|
}
|
|||
|
}
|
|||
|
if (duplicate != NULL) {
|
|||
|
Tcl_ClassSetMetadata((Tcl_Class) cls2Ptr, metadataTypePtr,
|
|||
|
duplicate);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
TclResetRewriteEnsemble(interp, 1);
|
|||
|
contextPtr = TclOOGetCallContext(o2Ptr, oPtr->fPtr->clonedName, 0, NULL);
|
|||
|
if (contextPtr) {
|
|||
|
args[0] = TclOOObjectName(interp, o2Ptr);
|
|||
|
args[1] = oPtr->fPtr->clonedName;
|
|||
|
args[2] = TclOOObjectName(interp, oPtr);
|
|||
|
Tcl_IncrRefCount(args[0]);
|
|||
|
Tcl_IncrRefCount(args[1]);
|
|||
|
Tcl_IncrRefCount(args[2]);
|
|||
|
result = Tcl_NRCallObjProc(interp, TclOOInvokeContext, contextPtr, 3,
|
|||
|
args);
|
|||
|
TclDecrRefCount(args[0]);
|
|||
|
TclDecrRefCount(args[1]);
|
|||
|
TclDecrRefCount(args[2]);
|
|||
|
TclOODeleteContext(contextPtr);
|
|||
|
if (result == TCL_ERROR) {
|
|||
|
Tcl_AddErrorInfo(interp,
|
|||
|
"\n (while performing post-copy callback)");
|
|||
|
}
|
|||
|
if (result != TCL_OK) {
|
|||
|
Tcl_DeleteCommandFromToken(interp, o2Ptr->command);
|
|||
|
return NULL;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return (Tcl_Object) o2Ptr;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* ----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* CloneObjectMethod, CloneClassMethod --
|
|||
|
*
|
|||
|
* Helper functions used for cloning methods. They work identically to
|
|||
|
* each other, except for the difference between them in how they
|
|||
|
* register the cloned method on a successful clone.
|
|||
|
*
|
|||
|
* ----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
static int
|
|||
|
CloneObjectMethod(
|
|||
|
Tcl_Interp *interp,
|
|||
|
Object *oPtr,
|
|||
|
Method *mPtr,
|
|||
|
Tcl_Obj *namePtr)
|
|||
|
{
|
|||
|
if (mPtr->typePtr == NULL) {
|
|||
|
Tcl_NewInstanceMethod(interp, (Tcl_Object) oPtr, namePtr,
|
|||
|
mPtr->flags & PUBLIC_METHOD, NULL, NULL);
|
|||
|
} else if (mPtr->typePtr->cloneProc) {
|
|||
|
ClientData newClientData;
|
|||
|
|
|||
|
if (mPtr->typePtr->cloneProc(interp, mPtr->clientData,
|
|||
|
&newClientData) != TCL_OK) {
|
|||
|
return TCL_ERROR;
|
|||
|
}
|
|||
|
Tcl_NewInstanceMethod(interp, (Tcl_Object) oPtr, namePtr,
|
|||
|
mPtr->flags & PUBLIC_METHOD, mPtr->typePtr, newClientData);
|
|||
|
} else {
|
|||
|
Tcl_NewInstanceMethod(interp, (Tcl_Object) oPtr, namePtr,
|
|||
|
mPtr->flags & PUBLIC_METHOD, mPtr->typePtr, mPtr->clientData);
|
|||
|
}
|
|||
|
return TCL_OK;
|
|||
|
}
|
|||
|
|
|||
|
static int
|
|||
|
CloneClassMethod(
|
|||
|
Tcl_Interp *interp,
|
|||
|
Class *clsPtr,
|
|||
|
Method *mPtr,
|
|||
|
Tcl_Obj *namePtr,
|
|||
|
Method **m2PtrPtr)
|
|||
|
{
|
|||
|
Method *m2Ptr;
|
|||
|
|
|||
|
if (mPtr->typePtr == NULL) {
|
|||
|
m2Ptr = (Method *) Tcl_NewMethod(interp, (Tcl_Class) clsPtr,
|
|||
|
namePtr, mPtr->flags & PUBLIC_METHOD, NULL, NULL);
|
|||
|
} else if (mPtr->typePtr->cloneProc) {
|
|||
|
ClientData newClientData;
|
|||
|
|
|||
|
if (mPtr->typePtr->cloneProc(interp, mPtr->clientData,
|
|||
|
&newClientData) != TCL_OK) {
|
|||
|
return TCL_ERROR;
|
|||
|
}
|
|||
|
m2Ptr = (Method *) Tcl_NewMethod(interp, (Tcl_Class) clsPtr,
|
|||
|
namePtr, mPtr->flags & PUBLIC_METHOD, mPtr->typePtr,
|
|||
|
newClientData);
|
|||
|
} else {
|
|||
|
m2Ptr = (Method *) Tcl_NewMethod(interp, (Tcl_Class) clsPtr,
|
|||
|
namePtr, mPtr->flags & PUBLIC_METHOD, mPtr->typePtr,
|
|||
|
mPtr->clientData);
|
|||
|
}
|
|||
|
if (m2PtrPtr != NULL) {
|
|||
|
*m2PtrPtr = m2Ptr;
|
|||
|
}
|
|||
|
return TCL_OK;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* ----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* Tcl_ClassGetMetadata, Tcl_ClassSetMetadata, Tcl_ObjectGetMetadata,
|
|||
|
* Tcl_ObjectSetMetadata --
|
|||
|
*
|
|||
|
* Metadata management API. The metadata system allows code in extensions
|
|||
|
* to attach arbitrary non-NULL pointers to objects and classes without
|
|||
|
* the different things that might be interested being able to interfere
|
|||
|
* with each other. Apart from non-NULL-ness, these routines attach no
|
|||
|
* interpretation to the meaning of the metadata pointers.
|
|||
|
*
|
|||
|
* The Tcl_*GetMetadata routines get the metadata pointer attached that
|
|||
|
* has been related with a particular type, or NULL if no metadata
|
|||
|
* associated with the given type has been attached.
|
|||
|
*
|
|||
|
* The Tcl_*SetMetadata routines set or delete the metadata pointer that
|
|||
|
* is related to a particular type. The value associated with the type is
|
|||
|
* deleted (if present; no-op otherwise) if the value is NULL, and
|
|||
|
* attached (replacing the previous value, which is deleted if present)
|
|||
|
* otherwise. This means it is impossible to attach a NULL value for any
|
|||
|
* metadata type.
|
|||
|
*
|
|||
|
* ----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
ClientData
|
|||
|
Tcl_ClassGetMetadata(
|
|||
|
Tcl_Class clazz,
|
|||
|
const Tcl_ObjectMetadataType *typePtr)
|
|||
|
{
|
|||
|
Class *clsPtr = (Class *) clazz;
|
|||
|
Tcl_HashEntry *hPtr;
|
|||
|
|
|||
|
/*
|
|||
|
* If there's no metadata store attached, the type in question has
|
|||
|
* definitely not been attached either!
|
|||
|
*/
|
|||
|
|
|||
|
if (clsPtr->metadataPtr == NULL) {
|
|||
|
return NULL;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* There is a metadata store, so look in it for the given type.
|
|||
|
*/
|
|||
|
|
|||
|
hPtr = Tcl_FindHashEntry(clsPtr->metadataPtr, (char *) typePtr);
|
|||
|
|
|||
|
/*
|
|||
|
* Return the metadata value if we found it, otherwise NULL.
|
|||
|
*/
|
|||
|
|
|||
|
if (hPtr == NULL) {
|
|||
|
return NULL;
|
|||
|
}
|
|||
|
return Tcl_GetHashValue(hPtr);
|
|||
|
}
|
|||
|
|
|||
|
void
|
|||
|
Tcl_ClassSetMetadata(
|
|||
|
Tcl_Class clazz,
|
|||
|
const Tcl_ObjectMetadataType *typePtr,
|
|||
|
ClientData metadata)
|
|||
|
{
|
|||
|
Class *clsPtr = (Class *) clazz;
|
|||
|
Tcl_HashEntry *hPtr;
|
|||
|
int isNew;
|
|||
|
|
|||
|
/*
|
|||
|
* Attach the metadata store if not done already.
|
|||
|
*/
|
|||
|
|
|||
|
if (clsPtr->metadataPtr == NULL) {
|
|||
|
if (metadata == NULL) {
|
|||
|
return;
|
|||
|
}
|
|||
|
clsPtr->metadataPtr = ckalloc(sizeof(Tcl_HashTable));
|
|||
|
Tcl_InitHashTable(clsPtr->metadataPtr, TCL_ONE_WORD_KEYS);
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* If the metadata is NULL, we're deleting the metadata for the type.
|
|||
|
*/
|
|||
|
|
|||
|
if (metadata == NULL) {
|
|||
|
hPtr = Tcl_FindHashEntry(clsPtr->metadataPtr, (char *) typePtr);
|
|||
|
if (hPtr != NULL) {
|
|||
|
typePtr->deleteProc(Tcl_GetHashValue(hPtr));
|
|||
|
Tcl_DeleteHashEntry(hPtr);
|
|||
|
}
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Otherwise we're attaching the metadata. Note that if there was already
|
|||
|
* some metadata attached of this type, we delete that first.
|
|||
|
*/
|
|||
|
|
|||
|
hPtr = Tcl_CreateHashEntry(clsPtr->metadataPtr, (char *) typePtr, &isNew);
|
|||
|
if (!isNew) {
|
|||
|
typePtr->deleteProc(Tcl_GetHashValue(hPtr));
|
|||
|
}
|
|||
|
Tcl_SetHashValue(hPtr, metadata);
|
|||
|
}
|
|||
|
|
|||
|
ClientData
|
|||
|
Tcl_ObjectGetMetadata(
|
|||
|
Tcl_Object object,
|
|||
|
const Tcl_ObjectMetadataType *typePtr)
|
|||
|
{
|
|||
|
Object *oPtr = (Object *) object;
|
|||
|
Tcl_HashEntry *hPtr;
|
|||
|
|
|||
|
/*
|
|||
|
* If there's no metadata store attached, the type in question has
|
|||
|
* definitely not been attached either!
|
|||
|
*/
|
|||
|
|
|||
|
if (oPtr->metadataPtr == NULL) {
|
|||
|
return NULL;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* There is a metadata store, so look in it for the given type.
|
|||
|
*/
|
|||
|
|
|||
|
hPtr = Tcl_FindHashEntry(oPtr->metadataPtr, (char *) typePtr);
|
|||
|
|
|||
|
/*
|
|||
|
* Return the metadata value if we found it, otherwise NULL.
|
|||
|
*/
|
|||
|
|
|||
|
if (hPtr == NULL) {
|
|||
|
return NULL;
|
|||
|
}
|
|||
|
return Tcl_GetHashValue(hPtr);
|
|||
|
}
|
|||
|
|
|||
|
void
|
|||
|
Tcl_ObjectSetMetadata(
|
|||
|
Tcl_Object object,
|
|||
|
const Tcl_ObjectMetadataType *typePtr,
|
|||
|
ClientData metadata)
|
|||
|
{
|
|||
|
Object *oPtr = (Object *) object;
|
|||
|
Tcl_HashEntry *hPtr;
|
|||
|
int isNew;
|
|||
|
|
|||
|
/*
|
|||
|
* Attach the metadata store if not done already.
|
|||
|
*/
|
|||
|
|
|||
|
if (oPtr->metadataPtr == NULL) {
|
|||
|
if (metadata == NULL) {
|
|||
|
return;
|
|||
|
}
|
|||
|
oPtr->metadataPtr = ckalloc(sizeof(Tcl_HashTable));
|
|||
|
Tcl_InitHashTable(oPtr->metadataPtr, TCL_ONE_WORD_KEYS);
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* If the metadata is NULL, we're deleting the metadata for the type.
|
|||
|
*/
|
|||
|
|
|||
|
if (metadata == NULL) {
|
|||
|
hPtr = Tcl_FindHashEntry(oPtr->metadataPtr, (char *) typePtr);
|
|||
|
if (hPtr != NULL) {
|
|||
|
typePtr->deleteProc(Tcl_GetHashValue(hPtr));
|
|||
|
Tcl_DeleteHashEntry(hPtr);
|
|||
|
}
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Otherwise we're attaching the metadata. Note that if there was already
|
|||
|
* some metadata attached of this type, we delete that first.
|
|||
|
*/
|
|||
|
|
|||
|
hPtr = Tcl_CreateHashEntry(oPtr->metadataPtr, (char *) typePtr, &isNew);
|
|||
|
if (!isNew) {
|
|||
|
typePtr->deleteProc(Tcl_GetHashValue(hPtr));
|
|||
|
}
|
|||
|
Tcl_SetHashValue(hPtr, metadata);
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* ----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* PublicObjectCmd, PrivateObjectCmd, TclOOInvokeObject --
|
|||
|
*
|
|||
|
* Main entry point for object invocations. The Public* and Private*
|
|||
|
* wrapper functions (implementations of both object instance commands
|
|||
|
* and [my]) are just thin wrappers round the main TclOOObjectCmdCore
|
|||
|
* function. Note that the core is function is NRE-aware.
|
|||
|
*
|
|||
|
* ----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
static int
|
|||
|
PublicObjectCmd(
|
|||
|
ClientData clientData,
|
|||
|
Tcl_Interp *interp,
|
|||
|
int objc,
|
|||
|
Tcl_Obj *const *objv)
|
|||
|
{
|
|||
|
return Tcl_NRCallObjProc(interp, PublicNRObjectCmd, clientData,objc,objv);
|
|||
|
}
|
|||
|
|
|||
|
static int
|
|||
|
PublicNRObjectCmd(
|
|||
|
ClientData clientData,
|
|||
|
Tcl_Interp *interp,
|
|||
|
int objc,
|
|||
|
Tcl_Obj *const *objv)
|
|||
|
{
|
|||
|
return TclOOObjectCmdCore(clientData, interp, objc, objv, PUBLIC_METHOD,
|
|||
|
NULL);
|
|||
|
}
|
|||
|
|
|||
|
static int
|
|||
|
PrivateObjectCmd(
|
|||
|
ClientData clientData,
|
|||
|
Tcl_Interp *interp,
|
|||
|
int objc,
|
|||
|
Tcl_Obj *const *objv)
|
|||
|
{
|
|||
|
return Tcl_NRCallObjProc(interp, PrivateNRObjectCmd,clientData,objc,objv);
|
|||
|
}
|
|||
|
|
|||
|
static int
|
|||
|
PrivateNRObjectCmd(
|
|||
|
ClientData clientData,
|
|||
|
Tcl_Interp *interp,
|
|||
|
int objc,
|
|||
|
Tcl_Obj *const *objv)
|
|||
|
{
|
|||
|
return TclOOObjectCmdCore(clientData, interp, objc, objv, 0, NULL);
|
|||
|
}
|
|||
|
|
|||
|
int
|
|||
|
TclOOInvokeObject(
|
|||
|
Tcl_Interp *interp, /* Interpreter for commands, variables,
|
|||
|
* results, error reporting, etc. */
|
|||
|
Tcl_Object object, /* The object to invoke. */
|
|||
|
Tcl_Class startCls, /* Where in the class chain to start the
|
|||
|
* invoke from, or NULL to traverse the whole
|
|||
|
* chain including filters. */
|
|||
|
int publicPrivate, /* Whether this is an invoke from a public
|
|||
|
* context (PUBLIC_METHOD), a private context
|
|||
|
* (PRIVATE_METHOD), or a *really* private
|
|||
|
* context (any other value; conventionally
|
|||
|
* 0). */
|
|||
|
int objc, /* Number of arguments. */
|
|||
|
Tcl_Obj *const *objv) /* Array of argument objects. It is assumed
|
|||
|
* that the name of the method to invoke will
|
|||
|
* be at index 1. */
|
|||
|
{
|
|||
|
switch (publicPrivate) {
|
|||
|
case PUBLIC_METHOD:
|
|||
|
return TclOOObjectCmdCore((Object *) object, interp, objc, objv,
|
|||
|
PUBLIC_METHOD, (Class *) startCls);
|
|||
|
case PRIVATE_METHOD:
|
|||
|
return TclOOObjectCmdCore((Object *) object, interp, objc, objv,
|
|||
|
PRIVATE_METHOD, (Class *) startCls);
|
|||
|
default:
|
|||
|
return TclOOObjectCmdCore((Object *) object, interp, objc, objv, 0,
|
|||
|
(Class *) startCls);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* ----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* TclOOObjectCmdCore, FinalizeObjectCall --
|
|||
|
*
|
|||
|
* Main function for object invocations. Does call chain creation,
|
|||
|
* management and invocation. The function FinalizeObjectCall exists to
|
|||
|
* clean up after the non-recursive processing of TclOOObjectCmdCore.
|
|||
|
*
|
|||
|
* ----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
int
|
|||
|
TclOOObjectCmdCore(
|
|||
|
Object *oPtr, /* The object being invoked. */
|
|||
|
Tcl_Interp *interp, /* The interpreter containing the object. */
|
|||
|
int objc, /* How many arguments are being passed in. */
|
|||
|
Tcl_Obj *const *objv, /* The array of arguments. */
|
|||
|
int flags, /* Whether this is an invocation through the
|
|||
|
* public or the private command interface. */
|
|||
|
Class *startCls) /* Where to start in the call chain, or NULL
|
|||
|
* if we are to start at the front with
|
|||
|
* filters and the object's methods (which is
|
|||
|
* the normal case). */
|
|||
|
{
|
|||
|
CallContext *contextPtr;
|
|||
|
Tcl_Obj *methodNamePtr;
|
|||
|
int result;
|
|||
|
|
|||
|
/*
|
|||
|
* If we've no method name, throw this directly into the unknown
|
|||
|
* processing.
|
|||
|
*/
|
|||
|
|
|||
|
if (objc < 2) {
|
|||
|
flags |= FORCE_UNKNOWN;
|
|||
|
methodNamePtr = NULL;
|
|||
|
goto noMapping;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Give plugged in code a chance to remap the method name.
|
|||
|
*/
|
|||
|
|
|||
|
methodNamePtr = objv[1];
|
|||
|
if (oPtr->mapMethodNameProc != NULL) {
|
|||
|
Class **startClsPtr = &startCls;
|
|||
|
Tcl_Obj *mappedMethodName = Tcl_DuplicateObj(methodNamePtr);
|
|||
|
|
|||
|
result = oPtr->mapMethodNameProc(interp, (Tcl_Object) oPtr,
|
|||
|
(Tcl_Class *) startClsPtr, mappedMethodName);
|
|||
|
if (result != TCL_OK) {
|
|||
|
TclDecrRefCount(mappedMethodName);
|
|||
|
if (result == TCL_BREAK) {
|
|||
|
goto noMapping;
|
|||
|
} else if (result == TCL_ERROR) {
|
|||
|
Tcl_AddErrorInfo(interp, "\n (while mapping method name)");
|
|||
|
}
|
|||
|
return result;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Get the call chain for the remapped name.
|
|||
|
*/
|
|||
|
|
|||
|
Tcl_IncrRefCount(mappedMethodName);
|
|||
|
contextPtr = TclOOGetCallContext(oPtr, mappedMethodName,
|
|||
|
flags | (oPtr->flags & FILTER_HANDLING), methodNamePtr);
|
|||
|
TclDecrRefCount(mappedMethodName);
|
|||
|
if (contextPtr == NULL) {
|
|||
|
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
|
|||
|
"impossible to invoke method \"%s\": no defined method or"
|
|||
|
" unknown method", TclGetString(methodNamePtr)));
|
|||
|
Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "METHOD_MAPPED",
|
|||
|
TclGetString(methodNamePtr), NULL);
|
|||
|
return TCL_ERROR;
|
|||
|
}
|
|||
|
} else {
|
|||
|
/*
|
|||
|
* Get the call chain.
|
|||
|
*/
|
|||
|
|
|||
|
noMapping:
|
|||
|
contextPtr = TclOOGetCallContext(oPtr, methodNamePtr,
|
|||
|
flags | (oPtr->flags & FILTER_HANDLING), NULL);
|
|||
|
if (contextPtr == NULL) {
|
|||
|
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
|
|||
|
"impossible to invoke method \"%s\": no defined method or"
|
|||
|
" unknown method", TclGetString(methodNamePtr)));
|
|||
|
Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "METHOD",
|
|||
|
TclGetString(methodNamePtr), NULL);
|
|||
|
return TCL_ERROR;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Check to see if we need to apply magical tricks to start part way
|
|||
|
* through the call chain.
|
|||
|
*/
|
|||
|
|
|||
|
if (startCls != NULL) {
|
|||
|
for (; contextPtr->index < contextPtr->callPtr->numChain;
|
|||
|
contextPtr->index++) {
|
|||
|
struct MInvoke *miPtr =
|
|||
|
&contextPtr->callPtr->chain[contextPtr->index];
|
|||
|
|
|||
|
if (miPtr->isFilter) {
|
|||
|
continue;
|
|||
|
}
|
|||
|
if (miPtr->mPtr->declaringClassPtr == startCls) {
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
if (contextPtr->index >= contextPtr->callPtr->numChain) {
|
|||
|
Tcl_SetObjResult(interp, Tcl_NewStringObj(
|
|||
|
"no valid method implementation", -1));
|
|||
|
Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "METHOD",
|
|||
|
TclGetString(methodNamePtr), NULL);
|
|||
|
TclOODeleteContext(contextPtr);
|
|||
|
return TCL_ERROR;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Invoke the call chain, locking the object structure against deletion
|
|||
|
* for the duration.
|
|||
|
*/
|
|||
|
|
|||
|
TclNRAddCallback(interp, FinalizeObjectCall, contextPtr, NULL,NULL,NULL);
|
|||
|
return TclOOInvokeContext(contextPtr, interp, objc, objv);
|
|||
|
}
|
|||
|
|
|||
|
static int
|
|||
|
FinalizeObjectCall(
|
|||
|
ClientData data[],
|
|||
|
Tcl_Interp *interp,
|
|||
|
int result)
|
|||
|
{
|
|||
|
/*
|
|||
|
* Dispose of the call chain, which drops the lock on the object's
|
|||
|
* structure.
|
|||
|
*/
|
|||
|
|
|||
|
TclOODeleteContext(data[0]);
|
|||
|
return result;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* ----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* Tcl_ObjectContextInvokeNext, TclNRObjectContextInvokeNext, FinalizeNext --
|
|||
|
*
|
|||
|
* Invokes the next stage of the call chain described in an object
|
|||
|
* context. This is the core of the implementation of the [next] command.
|
|||
|
* Does not do management of the call-frame stack. Available in public
|
|||
|
* (standard API) and private (NRE-aware) forms. FinalizeNext is a
|
|||
|
* private function used to clean up in the NRE case.
|
|||
|
*
|
|||
|
* ----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
int
|
|||
|
Tcl_ObjectContextInvokeNext(
|
|||
|
Tcl_Interp *interp,
|
|||
|
Tcl_ObjectContext context,
|
|||
|
int objc,
|
|||
|
Tcl_Obj *const *objv,
|
|||
|
int skip)
|
|||
|
{
|
|||
|
CallContext *contextPtr = (CallContext *) context;
|
|||
|
int savedIndex = contextPtr->index;
|
|||
|
int savedSkip = contextPtr->skip;
|
|||
|
int result;
|
|||
|
|
|||
|
if (contextPtr->index + 1 >= contextPtr->callPtr->numChain) {
|
|||
|
/*
|
|||
|
* We're at the end of the chain; generate an error message unless the
|
|||
|
* interpreter is being torn down, in which case we might be getting
|
|||
|
* here because of methods/destructors doing a [next] (or equivalent)
|
|||
|
* unexpectedly.
|
|||
|
*/
|
|||
|
|
|||
|
const char *methodType;
|
|||
|
|
|||
|
if (Tcl_InterpDeleted(interp)) {
|
|||
|
return TCL_OK;
|
|||
|
}
|
|||
|
|
|||
|
if (contextPtr->callPtr->flags & CONSTRUCTOR) {
|
|||
|
methodType = "constructor";
|
|||
|
} else if (contextPtr->callPtr->flags & DESTRUCTOR) {
|
|||
|
methodType = "destructor";
|
|||
|
} else {
|
|||
|
methodType = "method";
|
|||
|
}
|
|||
|
|
|||
|
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
|
|||
|
"no next %s implementation", methodType));
|
|||
|
Tcl_SetErrorCode(interp, "TCL", "OO", "NOTHING_NEXT", NULL);
|
|||
|
return TCL_ERROR;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Advance to the next method implementation in the chain in the method
|
|||
|
* call context while we process the body. However, need to adjust the
|
|||
|
* argument-skip control because we're guaranteed to have a single prefix
|
|||
|
* arg (i.e., 'next') and not the variable amount that can happen because
|
|||
|
* method invocations (i.e., '$obj meth' and 'my meth'), constructors
|
|||
|
* (i.e., '$cls new' and '$cls create obj') and destructors (no args at
|
|||
|
* all) come through the same code.
|
|||
|
*/
|
|||
|
|
|||
|
contextPtr->index++;
|
|||
|
contextPtr->skip = skip;
|
|||
|
|
|||
|
/*
|
|||
|
* Invoke the (advanced) method call context in the caller context.
|
|||
|
*/
|
|||
|
|
|||
|
result = Tcl_NRCallObjProc(interp, TclOOInvokeContext, contextPtr, objc,
|
|||
|
objv);
|
|||
|
|
|||
|
/*
|
|||
|
* Restore the call chain context index as we've finished the inner invoke
|
|||
|
* and want to operate in the outer context again.
|
|||
|
*/
|
|||
|
|
|||
|
contextPtr->index = savedIndex;
|
|||
|
contextPtr->skip = savedSkip;
|
|||
|
|
|||
|
return result;
|
|||
|
}
|
|||
|
|
|||
|
int
|
|||
|
TclNRObjectContextInvokeNext(
|
|||
|
Tcl_Interp *interp,
|
|||
|
Tcl_ObjectContext context,
|
|||
|
int objc,
|
|||
|
Tcl_Obj *const *objv,
|
|||
|
int skip)
|
|||
|
{
|
|||
|
CallContext *contextPtr = (CallContext *) context;
|
|||
|
|
|||
|
if (contextPtr->index + 1 >= contextPtr->callPtr->numChain) {
|
|||
|
/*
|
|||
|
* We're at the end of the chain; generate an error message unless the
|
|||
|
* interpreter is being torn down, in which case we might be getting
|
|||
|
* here because of methods/destructors doing a [next] (or equivalent)
|
|||
|
* unexpectedly.
|
|||
|
*/
|
|||
|
|
|||
|
const char *methodType;
|
|||
|
|
|||
|
if (Tcl_InterpDeleted(interp)) {
|
|||
|
return TCL_OK;
|
|||
|
}
|
|||
|
|
|||
|
if (contextPtr->callPtr->flags & CONSTRUCTOR) {
|
|||
|
methodType = "constructor";
|
|||
|
} else if (contextPtr->callPtr->flags & DESTRUCTOR) {
|
|||
|
methodType = "destructor";
|
|||
|
} else {
|
|||
|
methodType = "method";
|
|||
|
}
|
|||
|
|
|||
|
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
|
|||
|
"no next %s implementation", methodType));
|
|||
|
Tcl_SetErrorCode(interp, "TCL", "OO", "NOTHING_NEXT", NULL);
|
|||
|
return TCL_ERROR;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Advance to the next method implementation in the chain in the method
|
|||
|
* call context while we process the body. However, need to adjust the
|
|||
|
* argument-skip control because we're guaranteed to have a single prefix
|
|||
|
* arg (i.e., 'next') and not the variable amount that can happen because
|
|||
|
* method invocations (i.e., '$obj meth' and 'my meth'), constructors
|
|||
|
* (i.e., '$cls new' and '$cls create obj') and destructors (no args at
|
|||
|
* all) come through the same code.
|
|||
|
*/
|
|||
|
|
|||
|
TclNRAddCallback(interp, FinalizeNext, contextPtr,
|
|||
|
INT2PTR(contextPtr->index), INT2PTR(contextPtr->skip), NULL);
|
|||
|
contextPtr->index++;
|
|||
|
contextPtr->skip = skip;
|
|||
|
|
|||
|
/*
|
|||
|
* Invoke the (advanced) method call context in the caller context.
|
|||
|
*/
|
|||
|
|
|||
|
return TclOOInvokeContext(contextPtr, interp, objc, objv);
|
|||
|
}
|
|||
|
|
|||
|
static int
|
|||
|
FinalizeNext(
|
|||
|
ClientData data[],
|
|||
|
Tcl_Interp *interp,
|
|||
|
int result)
|
|||
|
{
|
|||
|
CallContext *contextPtr = data[0];
|
|||
|
|
|||
|
/*
|
|||
|
* Restore the call chain context index as we've finished the inner invoke
|
|||
|
* and want to operate in the outer context again.
|
|||
|
*/
|
|||
|
|
|||
|
contextPtr->index = PTR2INT(data[1]);
|
|||
|
contextPtr->skip = PTR2INT(data[2]);
|
|||
|
return result;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* ----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* Tcl_GetObjectFromObj --
|
|||
|
*
|
|||
|
* Utility function to get an object from a Tcl_Obj containing its name.
|
|||
|
*
|
|||
|
* ----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
Tcl_Object
|
|||
|
Tcl_GetObjectFromObj(
|
|||
|
Tcl_Interp *interp, /* Interpreter in which to locate the object.
|
|||
|
* Will have an error message placed in it if
|
|||
|
* the name does not refer to an object. */
|
|||
|
Tcl_Obj *objPtr) /* The name of the object to look up, which is
|
|||
|
* exactly the name of its public command. */
|
|||
|
{
|
|||
|
Command *cmdPtr = (Command *) Tcl_GetCommandFromObj(interp, objPtr);
|
|||
|
|
|||
|
if (cmdPtr == NULL) {
|
|||
|
goto notAnObject;
|
|||
|
}
|
|||
|
if (cmdPtr->objProc != PublicObjectCmd) {
|
|||
|
cmdPtr = (Command *) TclGetOriginalCommand((Tcl_Command) cmdPtr);
|
|||
|
if (cmdPtr == NULL || cmdPtr->objProc != PublicObjectCmd) {
|
|||
|
goto notAnObject;
|
|||
|
}
|
|||
|
}
|
|||
|
return cmdPtr->objClientData;
|
|||
|
|
|||
|
notAnObject:
|
|||
|
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
|
|||
|
"%s does not refer to an object", TclGetString(objPtr)));
|
|||
|
Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "OBJECT", TclGetString(objPtr),
|
|||
|
NULL);
|
|||
|
return NULL;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* ----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* TclOOIsReachable --
|
|||
|
*
|
|||
|
* Utility function that tests whether a class is a subclass (whether
|
|||
|
* directly or indirectly) of another class.
|
|||
|
*
|
|||
|
* ----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
int
|
|||
|
TclOOIsReachable(
|
|||
|
Class *targetPtr,
|
|||
|
Class *startPtr)
|
|||
|
{
|
|||
|
int i;
|
|||
|
Class *superPtr;
|
|||
|
|
|||
|
tailRecurse:
|
|||
|
if (startPtr == targetPtr) {
|
|||
|
return 1;
|
|||
|
}
|
|||
|
if (startPtr->superclasses.num == 1 && startPtr->mixins.num == 0) {
|
|||
|
startPtr = startPtr->superclasses.list[0];
|
|||
|
goto tailRecurse;
|
|||
|
}
|
|||
|
FOREACH(superPtr, startPtr->superclasses) {
|
|||
|
if (TclOOIsReachable(targetPtr, superPtr)) {
|
|||
|
return 1;
|
|||
|
}
|
|||
|
}
|
|||
|
FOREACH(superPtr, startPtr->mixins) {
|
|||
|
if (TclOOIsReachable(targetPtr, superPtr)) {
|
|||
|
return 1;
|
|||
|
}
|
|||
|
}
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* ----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* TclOOObjectName, Tcl_GetObjectName --
|
|||
|
*
|
|||
|
* Utility function that returns the name of the object. Note that this
|
|||
|
* simplifies cache management by keeping the code to do it in one place
|
|||
|
* and not sprayed all over. The value returned always has a reference
|
|||
|
* count of at least one.
|
|||
|
*
|
|||
|
* ----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
Tcl_Obj *
|
|||
|
TclOOObjectName(
|
|||
|
Tcl_Interp *interp,
|
|||
|
Object *oPtr)
|
|||
|
{
|
|||
|
Tcl_Obj *namePtr;
|
|||
|
|
|||
|
if (oPtr->cachedNameObj) {
|
|||
|
return oPtr->cachedNameObj;
|
|||
|
}
|
|||
|
namePtr = Tcl_NewObj();
|
|||
|
Tcl_GetCommandFullName(interp, oPtr->command, namePtr);
|
|||
|
Tcl_IncrRefCount(namePtr);
|
|||
|
oPtr->cachedNameObj = namePtr;
|
|||
|
return namePtr;
|
|||
|
}
|
|||
|
|
|||
|
Tcl_Obj *
|
|||
|
Tcl_GetObjectName(
|
|||
|
Tcl_Interp *interp,
|
|||
|
Tcl_Object object)
|
|||
|
{
|
|||
|
return TclOOObjectName(interp, (Object *) object);
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* ----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* assorted trivial 'getter' functions
|
|||
|
*
|
|||
|
* ----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
Tcl_Method
|
|||
|
Tcl_ObjectContextMethod(
|
|||
|
Tcl_ObjectContext context)
|
|||
|
{
|
|||
|
CallContext *contextPtr = (CallContext *) context;
|
|||
|
return (Tcl_Method) contextPtr->callPtr->chain[contextPtr->index].mPtr;
|
|||
|
}
|
|||
|
|
|||
|
int
|
|||
|
Tcl_ObjectContextIsFiltering(
|
|||
|
Tcl_ObjectContext context)
|
|||
|
{
|
|||
|
CallContext *contextPtr = (CallContext *) context;
|
|||
|
return contextPtr->callPtr->chain[contextPtr->index].isFilter;
|
|||
|
}
|
|||
|
|
|||
|
Tcl_Object
|
|||
|
Tcl_ObjectContextObject(
|
|||
|
Tcl_ObjectContext context)
|
|||
|
{
|
|||
|
return (Tcl_Object) ((CallContext *)context)->oPtr;
|
|||
|
}
|
|||
|
|
|||
|
int
|
|||
|
Tcl_ObjectContextSkippedArgs(
|
|||
|
Tcl_ObjectContext context)
|
|||
|
{
|
|||
|
return ((CallContext *)context)->skip;
|
|||
|
}
|
|||
|
|
|||
|
Tcl_Namespace *
|
|||
|
Tcl_GetObjectNamespace(
|
|||
|
Tcl_Object object)
|
|||
|
{
|
|||
|
return ((Object *)object)->namespacePtr;
|
|||
|
}
|
|||
|
|
|||
|
Tcl_Command
|
|||
|
Tcl_GetObjectCommand(
|
|||
|
Tcl_Object object)
|
|||
|
{
|
|||
|
return ((Object *)object)->command;
|
|||
|
}
|
|||
|
|
|||
|
Tcl_Class
|
|||
|
Tcl_GetObjectAsClass(
|
|||
|
Tcl_Object object)
|
|||
|
{
|
|||
|
return (Tcl_Class) ((Object *)object)->classPtr;
|
|||
|
}
|
|||
|
|
|||
|
int
|
|||
|
Tcl_ObjectDeleted(
|
|||
|
Tcl_Object object)
|
|||
|
{
|
|||
|
return ((Object *)object)->command == NULL;
|
|||
|
}
|
|||
|
|
|||
|
Tcl_Object
|
|||
|
Tcl_GetClassAsObject(
|
|||
|
Tcl_Class clazz)
|
|||
|
{
|
|||
|
return (Tcl_Object) ((Class *)clazz)->thisPtr;
|
|||
|
}
|
|||
|
|
|||
|
Tcl_ObjectMapMethodNameProc *
|
|||
|
Tcl_ObjectGetMethodNameMapper(
|
|||
|
Tcl_Object object)
|
|||
|
{
|
|||
|
return ((Object *) object)->mapMethodNameProc;
|
|||
|
}
|
|||
|
|
|||
|
void
|
|||
|
Tcl_ObjectSetMethodNameMapper(
|
|||
|
Tcl_Object object,
|
|||
|
Tcl_ObjectMapMethodNameProc *mapMethodNameProc)
|
|||
|
{
|
|||
|
((Object *) object)->mapMethodNameProc = mapMethodNameProc;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Local Variables:
|
|||
|
* mode: c
|
|||
|
* c-basic-offset: 4
|
|||
|
* fill-column: 78
|
|||
|
* End:
|
|||
|
*/
|