OpenCores
URL https://opencores.org/ocsvn/or1k/or1k/trunk

Subversion Repositories or1k

Compare Revisions

  • This comparison shows the changes necessary to convert path
    /or1k/trunk/insight/itcl/itcl/generic
    from Rev 578 to Rev 1765
    Reverse comparison

Rev 578 → Rev 1765

/itcl_methods.c
0,0 → 1,2557
/*
* ------------------------------------------------------------------------
* PACKAGE: [incr Tcl]
* DESCRIPTION: Object-Oriented Extensions to Tcl
*
* [incr Tcl] provides object-oriented extensions to Tcl, much as
* C++ provides object-oriented extensions to C. It provides a means
* of encapsulating related procedures together with their shared data
* in a local namespace that is hidden from the outside world. It
* promotes code re-use through inheritance. More than anything else,
* it encourages better organization of Tcl applications through the
* object-oriented paradigm, leading to code that is easier to
* understand and maintain.
*
* These procedures handle commands available within a class scope.
* In [incr Tcl], the term "method" is used for a procedure that has
* access to object-specific data, while the term "proc" is used for
* a procedure that has access only to common class data.
*
* ========================================================================
* AUTHOR: Michael J. McLennan
* Bell Labs Innovations for Lucent Technologies
* mmclennan@lucent.com
* http://www.tcltk.com/itcl
*
* RCS: $Id: itcl_methods.c,v 1.1.1.1 2002-01-16 10:24:46 markom Exp $
* ========================================================================
* Copyright (c) 1993-1998 Lucent Technologies, Inc.
* ------------------------------------------------------------------------
* See the file "license.terms" for information on usage and redistribution
* of this file, and for a DISCLAIMER OF ALL WARRANTIES.
*/
#include "itclInt.h"
#include "tclCompile.h"
 
/* CYGNUS LOCAL */
/* FIXME - it looks like Michael removed the dependance on these... */
#if 0
#ifdef __CYGWIN32__
 
/* On cygwin32, this is how we import these variables from the Tcl DLL. */
 
extern int *_imp__tclTraceCompile;
 
#define tclTraceCompile (*_imp__tclTraceCompile)
 
extern int *_imp__tclTraceExec;
 
#define tclTraceExec (*_imp__tclTraceExec)
 
extern Tcl_ObjType *_imp__tclByteCodeType;
 
#define tclByteCodeType (*_imp__tclByteCodeType)
 
#endif
#endif
/* END CYGNUS LOCAL */
 
/*
* FORWARD DECLARATIONS
*/
static int ItclParseConfig _ANSI_ARGS_((Tcl_Interp *interp,
int objc, Tcl_Obj *CONST objv[], ItclObject *contextObj,
int *rargc, ItclVarDefn ***rvars, char ***rvals));
 
static int ItclHandleConfig _ANSI_ARGS_((Tcl_Interp *interp,
int argc, ItclVarDefn **vars, char **vals, ItclObject *contextObj));
 
/*
* ------------------------------------------------------------------------
* Itcl_BodyCmd()
*
* Invoked by Tcl whenever the user issues an "itcl::body" command to
* define or redefine the implementation for a class method/proc.
* Handles the following syntax:
*
* itcl::body <class>::<func> <arglist> <body>
*
* Looks for an existing class member function with the name <func>,
* and if found, tries to assign the implementation. If an argument
* list was specified in the original declaration, it must match
* <arglist> or an error is flagged. If <body> has the form "@name"
* then it is treated as a reference to a C handling procedure;
* otherwise, it is taken as a body of Tcl statements.
*
* Returns TCL_OK/TCL_ERROR to indicate success/failure.
* ------------------------------------------------------------------------
*/
/* ARGSUSED */
int
Itcl_BodyCmd(dummy, interp, objc, objv)
ClientData dummy; /* unused */
Tcl_Interp *interp; /* current interpreter */
int objc; /* number of arguments */
Tcl_Obj *CONST objv[]; /* argument objects */
{
int status = TCL_OK;
 
char *head, *tail, *token, *arglist, *body;
ItclClass *cdefn;
ItclMemberFunc *mfunc;
Tcl_HashEntry *entry;
Tcl_DString buffer;
 
if (objc != 4) {
token = Tcl_GetStringFromObj(objv[0], (int*)NULL);
Tcl_AppendStringsToObj(Tcl_GetObjResult(interp),
"wrong # args: should be \"",
token, " class::func arglist body\"",
(char*)NULL);
return TCL_ERROR;
}
 
/*
* Parse the member name "namesp::namesp::class::func".
* Make sure that a class name was specified, and that the
* class exists.
*/
token = Tcl_GetStringFromObj(objv[1], (int*)NULL);
Itcl_ParseNamespPath(token, &buffer, &head, &tail);
 
if (!head || *head == '\0') {
Tcl_AppendStringsToObj(Tcl_GetObjResult(interp),
"missing class specifier for body declaration \"", token, "\"",
(char*)NULL);
status = TCL_ERROR;
goto bodyCmdDone;
}
 
cdefn = Itcl_FindClass(interp, head, /* autoload */ 1);
if (cdefn == NULL) {
status = TCL_ERROR;
goto bodyCmdDone;
}
 
/*
* Find the function and try to change its implementation.
* Note that command resolution table contains *all* functions,
* even those in a base class. Make sure that the class
* containing the method definition is the requested class.
*/
if (objc != 4) {
token = Tcl_GetStringFromObj(objv[0], (int*)NULL);
Tcl_AppendStringsToObj(Tcl_GetObjResult(interp),
"wrong # args: should be \"",
token, " class::func arglist body\"",
(char*)NULL);
status = TCL_ERROR;
goto bodyCmdDone;
}
 
mfunc = NULL;
entry = Tcl_FindHashEntry(&cdefn->resolveCmds, tail);
if (entry) {
mfunc = (ItclMemberFunc*)Tcl_GetHashValue(entry);
if (mfunc->member->classDefn != cdefn) {
mfunc = NULL;
}
}
 
if (mfunc == NULL) {
Tcl_AppendStringsToObj(Tcl_GetObjResult(interp),
"function \"", tail, "\" is not defined in class \"",
cdefn->fullname, "\"",
(char*)NULL);
status = TCL_ERROR;
goto bodyCmdDone;
}
 
arglist = Tcl_GetStringFromObj(objv[2], (int*)NULL);
body = Tcl_GetStringFromObj(objv[3], (int*)NULL);
 
if (Itcl_ChangeMemberFunc(interp, mfunc, arglist, body) != TCL_OK) {
status = TCL_ERROR;
goto bodyCmdDone;
}
 
bodyCmdDone:
Tcl_DStringFree(&buffer);
return status;
}
 
/*
* ------------------------------------------------------------------------
* Itcl_ConfigBodyCmd()
*
* Invoked by Tcl whenever the user issues an "itcl::configbody" command
* to define or redefine the configuration code associated with a
* public variable. Handles the following syntax:
*
* itcl::configbody <class>::<publicVar> <body>
*
* Looks for an existing public variable with the name <publicVar>,
* and if found, tries to assign the implementation. If <body> has
* the form "@name" then it is treated as a reference to a C handling
* procedure; otherwise, it is taken as a body of Tcl statements.
*
* Returns TCL_OK/TCL_ERROR to indicate success/failure.
* ------------------------------------------------------------------------
*/
/* ARGSUSED */
int
Itcl_ConfigBodyCmd(dummy, interp, objc, objv)
ClientData dummy; /* unused */
Tcl_Interp *interp; /* current interpreter */
int objc; /* number of arguments */
Tcl_Obj *CONST objv[]; /* argument objects */
{
int status = TCL_OK;
 
char *head, *tail, *token;
Tcl_DString buffer;
ItclClass *cdefn;
ItclVarLookup *vlookup;
ItclMember *member;
ItclMemberCode *mcode;
Tcl_HashEntry *entry;
 
if (objc != 3) {
Tcl_WrongNumArgs(interp, 1, objv, "class::option body");
return TCL_ERROR;
}
 
/*
* Parse the member name "namesp::namesp::class::option".
* Make sure that a class name was specified, and that the
* class exists.
*/
token = Tcl_GetStringFromObj(objv[1], (int*)NULL);
Itcl_ParseNamespPath(token, &buffer, &head, &tail);
 
if (!head || *head == '\0') {
Tcl_AppendStringsToObj(Tcl_GetObjResult(interp),
"missing class specifier for body declaration \"", token, "\"",
(char*)NULL);
status = TCL_ERROR;
goto configBodyCmdDone;
}
 
cdefn = Itcl_FindClass(interp, head, /* autoload */ 1);
if (cdefn == NULL) {
status = TCL_ERROR;
goto configBodyCmdDone;
}
 
/*
* Find the variable and change its implementation.
* Note that variable resolution table has *all* variables,
* even those in a base class. Make sure that the class
* containing the variable definition is the requested class.
*/
vlookup = NULL;
entry = Tcl_FindHashEntry(&cdefn->resolveVars, tail);
if (entry) {
vlookup = (ItclVarLookup*)Tcl_GetHashValue(entry);
if (vlookup->vdefn->member->classDefn != cdefn) {
vlookup = NULL;
}
}
 
if (vlookup == NULL) {
Tcl_AppendStringsToObj(Tcl_GetObjResult(interp),
"option \"", tail, "\" is not defined in class \"",
cdefn->fullname, "\"",
(char*)NULL);
status = TCL_ERROR;
goto configBodyCmdDone;
}
member = vlookup->vdefn->member;
 
if (member->protection != ITCL_PUBLIC) {
Tcl_AppendStringsToObj(Tcl_GetObjResult(interp),
"option \"", member->fullname,
"\" is not a public configuration option",
(char*)NULL);
status = TCL_ERROR;
goto configBodyCmdDone;
}
 
token = Tcl_GetStringFromObj(objv[2], (int*)NULL);
 
if (Itcl_CreateMemberCode(interp, cdefn, (char*)NULL, token,
&mcode) != TCL_OK) {
 
status = TCL_ERROR;
goto configBodyCmdDone;
}
 
Itcl_PreserveData((ClientData)mcode);
Itcl_EventuallyFree((ClientData)mcode, Itcl_DeleteMemberCode);
 
if (member->code) {
Itcl_ReleaseData((ClientData)member->code);
}
member->code = mcode;
 
configBodyCmdDone:
Tcl_DStringFree(&buffer);
return status;
}
 
/*
* ------------------------------------------------------------------------
* Itcl_CreateMethod()
*
* Installs a method into the namespace associated with a class.
* If another command with the same name is already installed, then
* it is overwritten.
*
* Returns TCL_OK on success, or TCL_ERROR (along with an error message
* in the specified interp) if anything goes wrong.
* ------------------------------------------------------------------------
*/
int
Itcl_CreateMethod(interp, cdefn, name, arglist, body)
Tcl_Interp* interp; /* interpreter managing this action */
ItclClass *cdefn; /* class definition */
char* name; /* name of new method */
char* arglist; /* space-separated list of arg names */
char* body; /* body of commands for the method */
{
ItclMemberFunc *mfunc;
Tcl_DString buffer;
 
/*
* Make sure that the method name does not contain anything
* goofy like a "::" scope qualifier.
*/
if (strstr(name,"::")) {
Tcl_AppendStringsToObj(Tcl_GetObjResult(interp),
"bad method name \"", name, "\"",
(char*)NULL);
return TCL_ERROR;
}
 
/*
* Create the method definition.
*/
if (Itcl_CreateMemberFunc(interp, cdefn, name, arglist, body, &mfunc)
!= TCL_OK) {
return TCL_ERROR;
}
 
/*
* Build a fully-qualified name for the method, and install
* the command handler.
*/
Tcl_DStringInit(&buffer);
Tcl_DStringAppend(&buffer, cdefn->namesp->fullName, -1);
Tcl_DStringAppend(&buffer, "::", 2);
Tcl_DStringAppend(&buffer, name, -1);
name = Tcl_DStringValue(&buffer);
 
Itcl_PreserveData((ClientData)mfunc);
mfunc->accessCmd = Tcl_CreateObjCommand(interp, name, Itcl_ExecMethod,
(ClientData)mfunc, Itcl_ReleaseData);
 
Tcl_DStringFree(&buffer);
return TCL_OK;
}
 
/*
* ------------------------------------------------------------------------
* Itcl_CreateProc()
*
* Installs a class proc into the namespace associated with a class.
* If another command with the same name is already installed, then
* it is overwritten. Returns TCL_OK on success, or TCL_ERROR (along
* with an error message in the specified interp) if anything goes
* wrong.
* ------------------------------------------------------------------------
*/
int
Itcl_CreateProc(interp, cdefn, name, arglist, body)
Tcl_Interp* interp; /* interpreter managing this action */
ItclClass *cdefn; /* class definition */
char* name; /* name of new proc */
char* arglist; /* space-separated list of arg names */
char* body; /* body of commands for the proc */
{
ItclMemberFunc *mfunc;
Tcl_DString buffer;
 
/*
* Make sure that the proc name does not contain anything
* goofy like a "::" scope qualifier.
*/
if (strstr(name,"::")) {
Tcl_AppendStringsToObj(Tcl_GetObjResult(interp),
"bad proc name \"", name, "\"",
(char*)NULL);
return TCL_ERROR;
}
 
/*
* Create the proc definition.
*/
if (Itcl_CreateMemberFunc(interp, cdefn, name, arglist, body, &mfunc)
!= TCL_OK) {
return TCL_ERROR;
}
 
/*
* Mark procs as "common". This distinguishes them from methods.
*/
mfunc->member->flags |= ITCL_COMMON;
 
/*
* Build a fully-qualified name for the proc, and install
* the command handler.
*/
Tcl_DStringInit(&buffer);
Tcl_DStringAppend(&buffer, cdefn->namesp->fullName, -1);
Tcl_DStringAppend(&buffer, "::", 2);
Tcl_DStringAppend(&buffer, name, -1);
name = Tcl_DStringValue(&buffer);
 
Itcl_PreserveData((ClientData)mfunc);
mfunc->accessCmd = Tcl_CreateObjCommand(interp, name, Itcl_ExecProc,
(ClientData)mfunc, Itcl_ReleaseData);
 
Tcl_DStringFree(&buffer);
return TCL_OK;
}
 
/*
* ------------------------------------------------------------------------
* Itcl_CreateMemberFunc()
*
* Creates the data record representing a member function. This
* includes the argument list and the body of the function. If the
* body is of the form "@name", then it is treated as a label for
* a C procedure registered by Itcl_RegisterC().
*
* If any errors are encountered, this procedure returns TCL_ERROR
* along with an error message in the interpreter. Otherwise, it
* returns TCL_OK, and "mfuncPtr" returns a pointer to the new
* member function.
* ------------------------------------------------------------------------
*/
int
Itcl_CreateMemberFunc(interp, cdefn, name, arglist, body, mfuncPtr)
Tcl_Interp* interp; /* interpreter managing this action */
ItclClass *cdefn; /* class definition */
char* name; /* name of new member */
char* arglist; /* space-separated list of arg names */
char* body; /* body of commands for the method */
ItclMemberFunc** mfuncPtr; /* returns: pointer to new method defn */
{
int newEntry;
ItclMemberFunc *mfunc;
ItclMemberCode *mcode;
Tcl_HashEntry *entry;
 
/*
* Add the member function to the list of functions for
* the class. Make sure that a member function with the
* same name doesn't already exist.
*/
entry = Tcl_CreateHashEntry(&cdefn->functions, name, &newEntry);
 
if (!newEntry) {
Tcl_AppendStringsToObj(Tcl_GetObjResult(interp),
"\"", name, "\" already defined in class \"",
cdefn->fullname, "\"",
(char*)NULL);
return TCL_ERROR;
}
 
/*
* Try to create the implementation for this command member.
*/
if (Itcl_CreateMemberCode(interp, cdefn, arglist, body,
&mcode) != TCL_OK) {
 
Tcl_DeleteHashEntry(entry);
return TCL_ERROR;
}
Itcl_PreserveData((ClientData)mcode);
Itcl_EventuallyFree((ClientData)mcode, Itcl_DeleteMemberCode);
 
/*
* Allocate a member function definition and return.
*/
mfunc = (ItclMemberFunc*)ckalloc(sizeof(ItclMemberFunc));
mfunc->member = Itcl_CreateMember(interp, cdefn, name);
mfunc->member->code = mcode;
 
if (mfunc->member->protection == ITCL_DEFAULT_PROTECT) {
mfunc->member->protection = ITCL_PUBLIC;
}
 
mfunc->arglist = NULL;
mfunc->argcount = 0;
mfunc->accessCmd = NULL;
 
if (arglist) {
mfunc->member->flags |= ITCL_ARG_SPEC;
}
if (mcode->arglist) {
Itcl_CreateArgList(interp, arglist, &mfunc->argcount, &mfunc->arglist);
}
 
if (strcmp(name,"constructor") == 0) {
mfunc->member->flags |= ITCL_CONSTRUCTOR;
}
if (strcmp(name,"destructor") == 0) {
mfunc->member->flags |= ITCL_DESTRUCTOR;
}
 
Tcl_SetHashValue(entry, (ClientData)mfunc);
Itcl_PreserveData((ClientData)mfunc);
Itcl_EventuallyFree((ClientData)mfunc, Itcl_DeleteMemberFunc);
 
*mfuncPtr = mfunc;
return TCL_OK;
}
 
/*
* ------------------------------------------------------------------------
* Itcl_ChangeMemberFunc()
*
* Modifies the data record representing a member function. This
* is usually the body of the function, but can include the argument
* list if it was not defined when the member was first created.
* If the body is of the form "@name", then it is treated as a label
* for a C procedure registered by Itcl_RegisterC().
*
* If any errors are encountered, this procedure returns TCL_ERROR
* along with an error message in the interpreter. Otherwise, it
* returns TCL_OK, and "mfuncPtr" returns a pointer to the new
* member function.
* ------------------------------------------------------------------------
*/
int
Itcl_ChangeMemberFunc(interp, mfunc, arglist, body)
Tcl_Interp* interp; /* interpreter managing this action */
ItclMemberFunc* mfunc; /* command member being changed */
char* arglist; /* space-separated list of arg names */
char* body; /* body of commands for the method */
{
ItclMemberCode *mcode = NULL;
Tcl_Obj *objPtr;
 
/*
* Try to create the implementation for this command member.
*/
if (Itcl_CreateMemberCode(interp, mfunc->member->classDefn,
arglist, body, &mcode) != TCL_OK) {
 
return TCL_ERROR;
}
 
/*
* If the argument list was defined when the function was
* created, compare the arg lists or usage strings to make sure
* that the interface is not being redefined.
*/
if ((mfunc->member->flags & ITCL_ARG_SPEC) != 0 &&
!Itcl_EquivArgLists(mfunc->arglist, mfunc->argcount,
mcode->arglist, mcode->argcount)) {
 
objPtr = Itcl_ArgList(mfunc->argcount, mfunc->arglist);
Tcl_IncrRefCount(objPtr);
 
Tcl_AppendStringsToObj(Tcl_GetObjResult(interp),
"argument list changed for function \"",
mfunc->member->fullname, "\": should be \"",
Tcl_GetStringFromObj(objPtr, (int*)NULL), "\"",
(char*)NULL);
Tcl_DecrRefCount(objPtr);
 
Itcl_DeleteMemberCode((char*)mcode);
return TCL_ERROR;
}
 
/*
* Free up the old implementation and install the new one.
*/
Itcl_PreserveData((ClientData)mcode);
Itcl_EventuallyFree((ClientData)mcode, Itcl_DeleteMemberCode);
 
Itcl_ReleaseData((ClientData)mfunc->member->code);
mfunc->member->code = mcode;
 
return TCL_OK;
}
 
/*
* ------------------------------------------------------------------------
* Itcl_DeleteMemberFunc()
*
* Destroys all data associated with the given member function definition.
* Usually invoked by the interpreter when a member function is deleted.
* ------------------------------------------------------------------------
*/
void
Itcl_DeleteMemberFunc(cdata)
char* cdata; /* pointer to member function definition */
{
ItclMemberFunc* mfunc = (ItclMemberFunc*)cdata;
 
if (mfunc) {
Itcl_DeleteMember(mfunc->member);
 
if (mfunc->arglist) {
Itcl_DeleteArgList(mfunc->arglist);
}
ckfree((char*)mfunc);
}
}
 
/*
* ------------------------------------------------------------------------
* Itcl_CreateMemberCode()
*
* Creates the data record representing the implementation behind a
* class member function. This includes the argument list and the body
* of the function. If the body is of the form "@name", then it is
* treated as a label for a C procedure registered by Itcl_RegisterC().
*
* The implementation is kept by the member function definition, and
* controlled by a preserve/release paradigm. That way, if it is in
* use while it is being redefined, it will stay around long enough
* to avoid a core dump.
*
* If any errors are encountered, this procedure returns TCL_ERROR
* along with an error message in the interpreter. Otherwise, it
* returns TCL_OK, and "mcodePtr" returns a pointer to the new
* implementation.
* ------------------------------------------------------------------------
*/
int
Itcl_CreateMemberCode(interp, cdefn, arglist, body, mcodePtr)
Tcl_Interp* interp; /* interpreter managing this action */
ItclClass *cdefn; /* class containing this member */
char* arglist; /* space-separated list of arg names */
char* body; /* body of commands for the method */
ItclMemberCode** mcodePtr; /* returns: pointer to new implementation */
{
int argc;
CompiledLocal *args, *localPtr;
ItclMemberCode *mcode;
Proc *procPtr;
 
/*
* Allocate some space to hold the implementation.
*/
mcode = (ItclMemberCode*)ckalloc(sizeof(ItclMemberCode));
mcode->flags = 0;
mcode->argcount = 0;
mcode->arglist = NULL;
mcode->procPtr = NULL;
mcode->cfunc.objCmd = NULL;
mcode->clientData = NULL;
 
if (arglist) {
if (Itcl_CreateArgList(interp, arglist, &argc, &args)
!= TCL_OK) {
 
Itcl_DeleteMemberCode((char*)mcode);
return TCL_ERROR;
}
mcode->argcount = argc;
mcode->arglist = args;
mcode->flags |= ITCL_ARG_SPEC;
} else {
argc = 0;
args = NULL;
}
 
/*
* Create a standard Tcl Proc representation for this code body.
* This is required, since the Tcl compiler looks for a proc
* when handling things such as the call frame context and
* compiled locals.
*/
procPtr = (Proc*)ckalloc(sizeof(Proc));
mcode->procPtr = procPtr;
 
procPtr->iPtr = (Interp*)interp;
procPtr->refCount = 1;
procPtr->cmdPtr = (Command*)ckalloc(sizeof(Command));
procPtr->cmdPtr->nsPtr = (Namespace*)cdefn->namesp;
 
if (body) {
procPtr->bodyPtr = Tcl_NewStringObj(body, -1);
Tcl_IncrRefCount(procPtr->bodyPtr);
} else {
procPtr->bodyPtr = NULL;
}
 
/*
* Plug the argument list into the "compiled locals" list.
*
* NOTE: The storage for this argument list is owned by
* the caller, so although we plug it in here, it is not
* our responsibility to free it.
*/
procPtr->firstLocalPtr = args;
procPtr->lastLocalPtr = NULL;
 
for (localPtr=mcode->arglist; localPtr; localPtr=localPtr->nextPtr) {
procPtr->lastLocalPtr = localPtr;
}
procPtr->numArgs = argc;
procPtr->numCompiledLocals = argc;
 
/*
* If the body definition starts with '@', then treat the value
* as a symbolic name for a C procedure.
*/
if (body == NULL) {
mcode->flags |= ITCL_IMPLEMENT_NONE;
}
else if (*body == '@') {
Tcl_CmdProc *argCmdProc;
Tcl_ObjCmdProc *objCmdProc;
ClientData cdata;
 
if (!Itcl_FindC(interp, body+1, &argCmdProc, &objCmdProc, &cdata)) {
Tcl_AppendStringsToObj(Tcl_GetObjResult(interp),
"no registered C procedure with name \"", body+1, "\"",
(char*)NULL);
Itcl_DeleteMemberCode((char*)mcode);
return TCL_ERROR;
}
 
if (objCmdProc != NULL) {
mcode->flags |= ITCL_IMPLEMENT_OBJCMD;
mcode->cfunc.objCmd = objCmdProc;
mcode->clientData = cdata;
}
else if (argCmdProc != NULL) {
mcode->flags |= ITCL_IMPLEMENT_ARGCMD;
mcode->cfunc.argCmd = argCmdProc;
mcode->clientData = cdata;
}
}
 
/*
* Otherwise, treat the body as a chunk of Tcl code.
*/
else {
mcode->flags |= ITCL_IMPLEMENT_TCL;
}
 
*mcodePtr = mcode;
return TCL_OK;
}
 
/*
* ------------------------------------------------------------------------
* Itcl_DeleteMemberCode()
*
* Destroys all data associated with the given command implementation.
* Invoked automatically by Itcl_ReleaseData() when the implementation
* is no longer being used.
* ------------------------------------------------------------------------
*/
void
Itcl_DeleteMemberCode(cdata)
char* cdata; /* pointer to member function definition */
{
ItclMemberCode* mcode = (ItclMemberCode*)cdata;
 
if (mcode->arglist) {
Itcl_DeleteArgList(mcode->arglist);
}
if (mcode->procPtr) {
ckfree((char*) mcode->procPtr->cmdPtr);
 
/* don't free compiled locals -- that is handled by arglist above */
 
if (mcode->procPtr->bodyPtr) {
Tcl_DecrRefCount(mcode->procPtr->bodyPtr);
}
ckfree((char*)mcode->procPtr);
}
ckfree((char*)mcode);
}
 
/*
* ------------------------------------------------------------------------
* Itcl_GetMemberCode()
*
* Makes sure that the implementation for an [incr Tcl] code body is
* ready to run. Note that a member function can be declared without
* being defined. The class definition may contain a declaration of
* the member function, but its body may be defined in a separate file.
* If an undefined function is encountered, this routine automatically
* attempts to autoload it. If the body is implemented via Tcl code,
* then it is compiled here as well.
*
* Returns TCL_ERROR (along with an error message in the interpreter)
* if an error is encountered, or if the implementation is not defined
* and cannot be autoloaded. Returns TCL_OK if implementation is
* ready to use.
* ------------------------------------------------------------------------
*/
int
Itcl_GetMemberCode(interp, member)
Tcl_Interp* interp; /* interpreter managing this action */
ItclMember* member; /* member containing code body */
{
ItclMemberCode *mcode = member->code;
 
int result;
 
/*
* If the implementation has not yet been defined, try to
* autoload it now.
*/
if ((mcode->flags & ITCL_IMPLEMENT_NONE) != 0) {
result = Tcl_VarEval(interp, "::auto_load ", member->fullname,
(char*)NULL);
 
if (result != TCL_OK) {
char msg[256];
sprintf(msg, "\n (while autoloading code for \"%.100s\")",
member->fullname);
Tcl_AddErrorInfo(interp, msg);
return result;
}
Tcl_ResetResult(interp); /* get rid of 1/0 status */
}
 
/*
* If the implementation is still not available, then
* autoloading must have failed.
*
* TRICKY NOTE: If code has been autoloaded, then the
* old mcode pointer is probably invalid. Go back to
* the member and look at the current code pointer again.
*/
mcode = member->code;
 
if ((mcode->flags & ITCL_IMPLEMENT_NONE) != 0) {
Tcl_AppendStringsToObj(Tcl_GetObjResult(interp),
"member function \"", member->fullname,
"\" is not defined and cannot be autoloaded",
(char*)NULL);
return TCL_ERROR;
}
 
/*
* If the member is a constructor and the class has an
* initialization command, compile it here.
*/
if ((member->flags & ITCL_CONSTRUCTOR) != 0 &&
(member->classDefn->initCode != NULL)) {
 
result = TclProcCompileProc(interp, mcode->procPtr,
member->classDefn->initCode, (Namespace*)member->classDefn->namesp,
"initialization code for", member->fullname);
 
if (result != TCL_OK) {
return result;
}
}
 
/*
* If the code body has a Tcl implementation, then compile it here.
*/
if ((mcode->flags & ITCL_IMPLEMENT_TCL) != 0) {
 
result = TclProcCompileProc(interp, mcode->procPtr,
mcode->procPtr->bodyPtr, (Namespace*)member->classDefn->namesp,
"body for", member->fullname);
 
if (result != TCL_OK) {
return result;
}
}
return TCL_OK;
}
 
/*
* ------------------------------------------------------------------------
* Itcl_EvalMemberCode()
*
* Used to execute an ItclMemberCode representation of a code
* fragment. This code may be a body of Tcl commands, or a C handler
* procedure.
*
* Executes the command with the given arguments (objc,objv) and
* returns an integer status code (TCL_OK/TCL_ERROR). Returns the
* result string or an error message in the interpreter.
* ------------------------------------------------------------------------
*/
int
Itcl_EvalMemberCode(interp, mfunc, member, contextObj, objc, objv)
Tcl_Interp *interp; /* current interpreter */
ItclMemberFunc *mfunc; /* member func, or NULL (for error messages) */
ItclMember *member; /* command member containing code */
ItclObject *contextObj; /* object context, or NULL */
int objc; /* number of arguments */
Tcl_Obj *CONST objv[]; /* argument objects */
{
int result = TCL_OK;
Tcl_CallFrame *oldFramePtr = NULL;
 
int i, transparent, newEntry;
ItclObjectInfo *info;
ItclMemberCode *mcode;
ItclContext context;
Tcl_CallFrame *framePtr, *transFramePtr;
 
/*
* If this code does not have an implementation yet, then
* try to autoload one. Also, if this is Tcl code, make sure
* that it's compiled and ready to use.
*/
if (Itcl_GetMemberCode(interp, member) != TCL_OK) {
return TCL_ERROR;
}
mcode = member->code;
 
/*
* Bump the reference count on this code, in case it is
* redefined or deleted during execution.
*/
Itcl_PreserveData((ClientData)mcode);
 
/*
* Install a new call frame context for the current code.
* If the current call frame is marked as "transparent", then
* do an "uplevel" operation to move past it. Transparent
* call frames are installed by Itcl_HandleInstance. They
* provide a way of entering an object context without
* interfering with the normal call stack.
*/
transparent = 0;
 
info = member->classDefn->info;
framePtr = _Tcl_GetCallFrame(interp, 0);
for (i = Itcl_GetStackSize(&info->transparentFrames)-1; i >= 0; i--) {
transFramePtr = (Tcl_CallFrame*)
Itcl_GetStackValue(&info->transparentFrames, i);
 
if (framePtr == transFramePtr) {
transparent = 1;
break;
}
}
 
if (transparent) {
framePtr = _Tcl_GetCallFrame(interp, 1);
oldFramePtr = _Tcl_ActivateCallFrame(interp, framePtr);
}
 
if (Itcl_PushContext(interp, member, member->classDefn, contextObj,
&context) != TCL_OK) {
 
return TCL_ERROR;
}
 
/*
* If this is a method with a Tcl implementation, or a
* constructor with initCode, then parse its arguments now.
*/
if (mfunc && objc > 0) {
if ((mcode->flags & ITCL_IMPLEMENT_TCL) != 0 ||
( (member->flags & ITCL_CONSTRUCTOR) != 0 &&
(member->classDefn->initCode != NULL) ) ) {
 
if (Itcl_AssignArgs(interp, objc, objv, mfunc) != TCL_OK) {
result = TCL_ERROR;
goto evalMemberCodeDone;
}
}
}
 
/*
* If this code is a constructor, and if it is being invoked
* when an object is first constructed (i.e., the "constructed"
* table is still active within the object), then handle the
* "initCode" associated with the constructor and make sure that
* all base classes are properly constructed.
*
* TRICKY NOTE:
* The "initCode" must be executed here. This is the only
* opportunity where the arguments of the constructor are
* available in a call frame.
*/
if ((member->flags & ITCL_CONSTRUCTOR) && contextObj &&
contextObj->constructed) {
 
result = Itcl_ConstructBase(interp, contextObj, member->classDefn);
 
if (result != TCL_OK) {
goto evalMemberCodeDone;
}
}
 
/*
* Execute the code body...
*/
if ((mcode->flags & ITCL_IMPLEMENT_OBJCMD) != 0) {
result = (*mcode->cfunc.objCmd)(mcode->clientData,
interp, objc, objv);
}
else if ((mcode->flags & ITCL_IMPLEMENT_ARGCMD) != 0) {
char **argv;
argv = (char**)ckalloc( (unsigned)(objc*sizeof(char*)) );
for (i=0; i < objc; i++) {
argv[i] = Tcl_GetStringFromObj(objv[i], (int*)NULL);
}
 
result = (*mcode->cfunc.argCmd)(mcode->clientData,
interp, objc, argv);
 
ckfree((char*)argv);
}
else if ((mcode->flags & ITCL_IMPLEMENT_TCL) != 0) {
/* CYGNUS LOCAL - Fix for Tcl8.1 */
#if TCL_MAJOR_VERSION == 8 && TCL_MINOR_VERSION == 0
result = Tcl_EvalObj(interp, mcode->procPtr->bodyPtr);
#else
result = Tcl_EvalObj(interp, mcode->procPtr->bodyPtr, 0);
#endif
/* END CYGNUS LOCAL */
}
else {
panic("itcl: bad implementation flag for %s", member->fullname);
}
 
/*
* If this is a constructor or destructor, and if it is being
* invoked at the appropriate time, keep track of which methods
* have been called. This information is used to implicitly
* invoke constructors/destructors as needed.
*/
if ((member->flags & ITCL_DESTRUCTOR) && contextObj &&
contextObj->destructed) {
 
Tcl_CreateHashEntry(contextObj->destructed,
member->classDefn->name, &newEntry);
}
if ((member->flags & ITCL_CONSTRUCTOR) && contextObj &&
contextObj->constructed) {
 
Tcl_CreateHashEntry(contextObj->constructed,
member->classDefn->name, &newEntry);
}
 
evalMemberCodeDone:
Itcl_PopContext(interp, &context);
 
if (transparent) {
(void) _Tcl_ActivateCallFrame(interp, oldFramePtr);
}
Itcl_ReleaseData((ClientData)mcode);
 
return result;
}
 
/*
* ------------------------------------------------------------------------
* Itcl_CreateArgList()
*
* Parses a Tcl list representing an argument declaration and returns
* a linked list of CompiledLocal values. Usually invoked as part
* of Itcl_CreateMemberFunc() when a new method or procedure is being
* defined.
* ------------------------------------------------------------------------
*/
int
Itcl_CreateArgList(interp, decl, argcPtr, argPtr)
Tcl_Interp* interp; /* interpreter managing this function */
char* decl; /* string representing argument list */
int* argcPtr; /* returns number of args in argument list */
CompiledLocal** argPtr; /* returns pointer to parsed argument list */
{
int status = TCL_OK; /* assume that this will succeed */
 
int i, argc, fargc;
char **argv, **fargv;
CompiledLocal *localPtr, *last;
 
*argPtr = last = NULL;
*argcPtr = 0;
 
if (decl) {
if (Tcl_SplitList(interp, decl, &argc, &argv) != TCL_OK) {
return TCL_ERROR;
}
 
for (i=0; i < argc && status == TCL_OK; i++) {
if (Tcl_SplitList(interp, argv[i], &fargc, &fargv) != TCL_OK) {
status = TCL_ERROR;
}
else {
localPtr = NULL;
 
if (fargc == 0 || *fargv[0] == '\0') {
char mesg[100];
sprintf(mesg, "argument #%d has no name", i);
Tcl_SetResult(interp, mesg, TCL_VOLATILE);
status = TCL_ERROR;
}
else if (fargc > 2) {
Tcl_AppendStringsToObj(Tcl_GetObjResult(interp),
"too many fields in argument specifier \"",
argv[i], "\"",
(char*)NULL);
status = TCL_ERROR;
}
else if (strstr(fargv[0],"::")) {
Tcl_AppendStringsToObj(Tcl_GetObjResult(interp),
"bad argument name \"", fargv[0], "\"",
(char*)NULL);
status = TCL_ERROR;
}
else if (fargc == 1) {
localPtr = Itcl_CreateArg(fargv[0], (char*)NULL);
}
else {
localPtr = Itcl_CreateArg(fargv[0], fargv[1]);
}
 
if (localPtr) {
localPtr->frameIndex = i;
 
if (*argPtr == NULL) {
*argPtr = last = localPtr;
}
else {
last->nextPtr = localPtr;
last = localPtr;
}
}
}
ckfree((char*)fargv);
}
ckfree((char*)argv);
}
 
/*
* If anything went wrong, destroy whatever arguments were
* created and return an error.
*/
if (status == TCL_OK) {
*argcPtr = argc;
} else {
Itcl_DeleteArgList(*argPtr);
*argPtr = NULL;
}
return status;
}
 
/*
* ------------------------------------------------------------------------
* Itcl_CreateArg()
*
* Creates a new Tcl Arg structure and fills it with the given
* information. Returns a pointer to the new Arg structure.
* ------------------------------------------------------------------------
*/
CompiledLocal*
Itcl_CreateArg(name, init)
char* name; /* name of new argument */
char* init; /* initial value */
{
CompiledLocal *localPtr = NULL;
int nameLen;
 
if (name == NULL) {
name = "";
}
nameLen = strlen(name);
 
localPtr = (CompiledLocal*)ckalloc(
(unsigned)(sizeof(CompiledLocal)-sizeof(localPtr->name) + nameLen+1)
);
 
localPtr->nextPtr = NULL;
localPtr->nameLength = nameLen;
localPtr->frameIndex = 0; /* set this later */
localPtr->flags = VAR_SCALAR | VAR_ARGUMENT;
localPtr->resolveInfo = NULL;
 
if (init != NULL) {
localPtr->defValuePtr = Tcl_NewStringObj(init, -1);
Tcl_IncrRefCount(localPtr->defValuePtr);
} else {
localPtr->defValuePtr = NULL;
}
 
strcpy(localPtr->name, name);
 
return localPtr;
}
/*
* ------------------------------------------------------------------------
* Itcl_DeleteArgList()
*
* Destroys a chain of arguments acting as an argument list. Usually
* invoked when a method/proc is being destroyed, to discard its
* argument list.
* ------------------------------------------------------------------------
*/
void
Itcl_DeleteArgList(arglist)
CompiledLocal *arglist; /* first argument in arg list chain */
{
CompiledLocal *localPtr, *next;
 
for (localPtr=arglist; localPtr; localPtr=next) {
if (localPtr->defValuePtr != NULL) {
Tcl_DecrRefCount(localPtr->defValuePtr);
}
if (localPtr->resolveInfo) {
if (localPtr->resolveInfo->deleteProc) {
localPtr->resolveInfo->deleteProc(localPtr->resolveInfo);
} else {
ckfree((char*)localPtr->resolveInfo);
}
localPtr->resolveInfo = NULL;
}
next = localPtr->nextPtr;
ckfree((char*)localPtr);
}
}
/*
* ------------------------------------------------------------------------
* Itcl_ArgList()
*
* Returns a Tcl_Obj containing the string representation for the
* given argument list. This object has a reference count of 1.
* The reference count should be decremented when the string is no
* longer needed, and it will free itself.
* ------------------------------------------------------------------------
*/
Tcl_Obj*
Itcl_ArgList(argc, arglist)
int argc; /* number of arguments */
CompiledLocal* arglist; /* first argument in arglist */
{
char *val;
Tcl_Obj *objPtr;
Tcl_DString buffer;
 
Tcl_DStringInit(&buffer);
 
while (arglist && argc-- > 0) {
if (arglist->defValuePtr) {
val = Tcl_GetStringFromObj(arglist->defValuePtr, (int*)NULL);
Tcl_DStringStartSublist(&buffer);
Tcl_DStringAppendElement(&buffer, arglist->name);
Tcl_DStringAppendElement(&buffer, val);
Tcl_DStringEndSublist(&buffer);
}
else {
Tcl_DStringAppendElement(&buffer, arglist->name);
}
arglist = arglist->nextPtr;
}
 
objPtr = Tcl_NewStringObj(Tcl_DStringValue(&buffer),
Tcl_DStringLength(&buffer));
 
Tcl_DStringFree(&buffer);
 
return objPtr;
}
 
/*
* ------------------------------------------------------------------------
* Itcl_EquivArgLists()
*
* Compares two argument lists to see if they are equivalent. The
* first list is treated as a prototype, and the second list must
* match it. Argument names may be different, but they must match in
* meaning. If one argument is optional, the corresponding argument
* must also be optional. If the prototype list ends with the magic
* "args" argument, then it matches everything in the other list.
*
* Returns non-zero if the argument lists are equivalent.
* ------------------------------------------------------------------------
*/
int
Itcl_EquivArgLists(arg1, arg1c, arg2, arg2c)
CompiledLocal* arg1; /* prototype argument list */
int arg1c; /* number of args in prototype arg list */
CompiledLocal* arg2; /* another argument list to match against */
int arg2c; /* number of args in matching list */
{
char *dval1, *dval2;
 
while (arg1 && arg1c > 0 && arg2 && arg2c > 0) {
/*
* If the prototype argument list ends with the magic "args"
* argument, then it matches everything in the other list.
*/
if (arg1c == 1 && strcmp(arg1->name,"args") == 0) {
return 1;
}
 
/*
* If one has a default value, then the other must have the
* same default value.
*/
if (arg1->defValuePtr) {
if (arg2->defValuePtr == NULL) {
return 0;
}
 
dval1 = Tcl_GetStringFromObj(arg1->defValuePtr, (int*)NULL);
dval2 = Tcl_GetStringFromObj(arg2->defValuePtr, (int*)NULL);
if (strcmp(dval1, dval2) != 0) {
return 0;
}
}
else if (arg2->defValuePtr) {
return 0;
}
 
arg1 = arg1->nextPtr; arg1c--;
arg2 = arg2->nextPtr; arg2c--;
}
if (arg1c == 1 && strcmp(arg1->name,"args") == 0) {
return 1;
}
return (arg1c == 0 && arg2c == 0);
}
 
/*
* ------------------------------------------------------------------------
* Itcl_GetMemberFuncUsage()
*
* Returns a string showing how a command member should be invoked.
* If the command member is a method, then the specified object name
* is reported as part of the invocation path:
*
* obj method arg ?arg arg ...?
*
* Otherwise, the "obj" pointer is ignored, and the class name is
* used as the invocation path:
*
* class::proc arg ?arg arg ...?
*
* Returns the string by appending it onto the Tcl_Obj passed in as
* an argument.
* ------------------------------------------------------------------------
*/
void
Itcl_GetMemberFuncUsage(mfunc, contextObj, objPtr)
ItclMemberFunc *mfunc; /* command member being examined */
ItclObject *contextObj; /* invoked with respect to this object */
Tcl_Obj *objPtr; /* returns: string showing usage */
{
int argcount;
char *name;
CompiledLocal *arglist, *argPtr;
Tcl_HashEntry *entry;
ItclMemberFunc *mf;
ItclClass *cdefnPtr;
 
/*
* If the command is a method and an object context was
* specified, then add the object context. If the method
* was a constructor, and if the object is being created,
* then report the invocation via the class creation command.
*/
if ((mfunc->member->flags & ITCL_COMMON) == 0) {
if ((mfunc->member->flags & ITCL_CONSTRUCTOR) != 0 &&
contextObj->constructed) {
 
cdefnPtr = (ItclClass*)contextObj->classDefn;
mf = NULL;
entry = Tcl_FindHashEntry(&cdefnPtr->resolveCmds, "constructor");
if (entry) {
mf = (ItclMemberFunc*)Tcl_GetHashValue(entry);
}
 
if (mf == mfunc) {
Tcl_GetCommandFullName(contextObj->classDefn->interp,
contextObj->classDefn->accessCmd, objPtr);
Tcl_AppendToObj(objPtr, " ", -1);
name = Tcl_GetCommandName(contextObj->classDefn->interp,
contextObj->accessCmd);
Tcl_AppendToObj(objPtr, name, -1);
} else {
Tcl_AppendToObj(objPtr, mfunc->member->fullname, -1);
}
}
else if (contextObj && contextObj->accessCmd) {
name = Tcl_GetCommandName(contextObj->classDefn->interp,
contextObj->accessCmd);
Tcl_AppendStringsToObj(objPtr, name, " ", mfunc->member->name,
(char*)NULL);
}
else {
Tcl_AppendStringsToObj(objPtr, "<object> ", mfunc->member->name,
(char*)NULL);
}
}
else {
Tcl_AppendToObj(objPtr, mfunc->member->fullname, -1);
}
 
/*
* Add the argument usage info.
*/
if (mfunc->member->code) {
arglist = mfunc->member->code->arglist;
argcount = mfunc->member->code->argcount;
} else if (mfunc->arglist) {
arglist = mfunc->arglist;
argcount = mfunc->argcount;
} else {
arglist = NULL;
argcount = 0;
}
 
if (arglist) {
for (argPtr=arglist;
argPtr && argcount > 0;
argPtr=argPtr->nextPtr, argcount--) {
 
if (argcount == 1 && strcmp(argPtr->name, "args") == 0) {
Tcl_AppendToObj(objPtr, " ?arg arg ...?", -1);
}
else if (argPtr->defValuePtr) {
Tcl_AppendStringsToObj(objPtr, " ?", argPtr->name, "?",
(char*)NULL);
}
else {
Tcl_AppendStringsToObj(objPtr, " ", argPtr->name,
(char*)NULL);
}
}
}
}
 
/*
* ------------------------------------------------------------------------
* Itcl_ExecMethod()
*
* Invoked by Tcl to handle the execution of a user-defined method.
* A method is similar to the usual Tcl proc, but has access to
* object-specific data. If for some reason there is no current
* object context, then a method call is inappropriate, and an error
* is returned.
*
* Methods are implemented either as Tcl code fragments, or as C-coded
* procedures. For Tcl code fragments, command arguments are parsed
* according to the argument list, and the body is executed in the
* scope of the class where it was defined. For C procedures, the
* arguments are passed in "as-is", and the procedure is executed in
* the most-specific class scope.
* ------------------------------------------------------------------------
*/
int
Itcl_ExecMethod(clientData, interp, objc, objv)
ClientData clientData; /* method definition */
Tcl_Interp *interp; /* current interpreter */
int objc; /* number of arguments */
Tcl_Obj *CONST objv[]; /* argument objects */
{
ItclMemberFunc *mfunc = (ItclMemberFunc*)clientData;
ItclMember *member = mfunc->member;
int result = TCL_OK;
 
char *token;
Tcl_HashEntry *entry;
ItclClass *contextClass;
ItclObject *contextObj;
 
/*
* Make sure that the current namespace context includes an
* object that is being manipulated. Methods can be executed
* only if an object context exists.
*/
if (Itcl_GetContext(interp, &contextClass, &contextObj) != TCL_OK) {
return TCL_ERROR;
}
if (contextObj == NULL) {
Tcl_AppendStringsToObj(Tcl_GetObjResult(interp),
"cannot access object-specific info without an object context",
(char*)NULL);
return TCL_ERROR;
}
 
/*
* Make sure that this command member can be accessed from
* the current namespace context.
*/
if (mfunc->member->protection != ITCL_PUBLIC) {
Tcl_Namespace *contextNs = Itcl_GetTrueNamespace(interp,
contextClass->info);
 
if (!Itcl_CanAccessFunc(mfunc, contextNs)) {
Tcl_AppendStringsToObj(Tcl_GetObjResult(interp),
"can't access \"", member->fullname, "\": ",
Itcl_ProtectionStr(member->protection), " function",
(char*)NULL);
return TCL_ERROR;
}
}
 
/*
* All methods should be "virtual" unless they are invoked with
* a "::" scope qualifier.
*
* To implement the "virtual" behavior, find the most-specific
* implementation for the method by looking in the "resolveCmds"
* table for this class.
*/
token = Tcl_GetStringFromObj(objv[0], (int*)NULL);
if (strstr(token, "::") == NULL) {
entry = Tcl_FindHashEntry(&contextObj->classDefn->resolveCmds,
member->name);
 
if (entry) {
mfunc = (ItclMemberFunc*)Tcl_GetHashValue(entry);
member = mfunc->member;
}
}
 
/*
* Execute the code for the method. Be careful to protect
* the method in case it gets deleted during execution.
*/
Itcl_PreserveData((ClientData)mfunc);
 
result = Itcl_EvalMemberCode(interp, mfunc, member, contextObj,
objc, objv);
 
result = Itcl_ReportFuncErrors(interp, mfunc, contextObj, result);
 
Itcl_ReleaseData((ClientData)mfunc);
 
return result;
}
 
/*
* ------------------------------------------------------------------------
* Itcl_ExecProc()
*
* Invoked by Tcl to handle the execution of a user-defined proc.
*
* Procs are implemented either as Tcl code fragments, or as C-coded
* procedures. For Tcl code fragments, command arguments are parsed
* according to the argument list, and the body is executed in the
* scope of the class where it was defined. For C procedures, the
* arguments are passed in "as-is", and the procedure is executed in
* the most-specific class scope.
* ------------------------------------------------------------------------
*/
int
Itcl_ExecProc(clientData, interp, objc, objv)
ClientData clientData; /* proc definition */
Tcl_Interp *interp; /* current interpreter */
int objc; /* number of arguments */
Tcl_Obj *CONST objv[]; /* argument objects */
{
ItclMemberFunc *mfunc = (ItclMemberFunc*)clientData;
ItclMember *member = mfunc->member;
int result = TCL_OK;
 
/*
* Make sure that this command member can be accessed from
* the current namespace context.
*/
if (mfunc->member->protection != ITCL_PUBLIC) {
Tcl_Namespace *contextNs = Itcl_GetTrueNamespace(interp,
mfunc->member->classDefn->info);
 
if (!Itcl_CanAccessFunc(mfunc, contextNs)) {
Tcl_AppendStringsToObj(Tcl_GetObjResult(interp),
"can't access \"", member->fullname, "\": ",
Itcl_ProtectionStr(member->protection), " function",
(char*)NULL);
return TCL_ERROR;
}
}
 
/*
* Execute the code for the proc. Be careful to protect
* the proc in case it gets deleted during execution.
*/
Itcl_PreserveData((ClientData)mfunc);
 
result = Itcl_EvalMemberCode(interp, mfunc, member, (ItclObject*)NULL,
objc, objv);
 
result = Itcl_ReportFuncErrors(interp, mfunc, (ItclObject*)NULL, result);
 
Itcl_ReleaseData((ClientData)mfunc);
 
return result;
}
 
/*
* ------------------------------------------------------------------------
* Itcl_PushContext()
*
* Sets up the class/object context so that a body of [incr Tcl]
* code can be executed. This procedure pushes a call frame with
* the proper namespace context for the class. If an object context
* is supplied, the object's instance variables are integrated into
* the call frame so they can be accessed as local variables.
* ------------------------------------------------------------------------
*/
int
Itcl_PushContext(interp, member, contextClass, contextObj, contextPtr)
Tcl_Interp *interp; /* interpreter managing this body of code */
ItclMember *member; /* member containing code body */
ItclClass *contextClass; /* class context */
ItclObject *contextObj; /* object context, or NULL */
ItclContext *contextPtr; /* storage space for class/object context */
{
CallFrame *framePtr = &contextPtr->frame;
 
int result, localCt, newEntry;
ItclMemberCode *mcode;
Proc *procPtr;
Tcl_HashEntry *entry;
 
/*
* Activate the call frame. If this fails, we'll bail out
* before allocating any resources.
*
* NOTE: Always push a call frame that looks like a proc.
* This causes global variables to be handled properly
* inside methods/procs.
*/
result = Tcl_PushCallFrame(interp, (Tcl_CallFrame*)framePtr,
contextClass->namesp, /* isProcCallFrame */ 1);
 
if (result != TCL_OK) {
return result;
}
 
contextPtr->classDefn = contextClass;
contextPtr->compiledLocals = &contextPtr->localStorage[0];
 
/*
* If this is an object context, register it in a hash table
* of all known contexts. We'll need this later if we
* call Itcl_GetContext to get the object context for the
* current call frame.
*/
if (contextObj) {
entry = Tcl_CreateHashEntry(&contextClass->info->contextFrames,
(char*)framePtr, &newEntry);
 
Itcl_PreserveData((ClientData)contextObj);
Tcl_SetHashValue(entry, (ClientData)contextObj);
}
 
/*
* Set up the compiled locals in the call frame and assign
* argument variables.
*/
if (member) {
mcode = member->code;
procPtr = mcode->procPtr;
 
/*
* If there are too many compiled locals to fit in the default
* storage space for the context, then allocate more space.
*/
localCt = procPtr->numCompiledLocals;
if (localCt > sizeof(contextPtr->localStorage)/sizeof(Var)) {
contextPtr->compiledLocals = (Var*)ckalloc(
(unsigned)(localCt * sizeof(Var))
);
}
 
/*
* Initialize and resolve compiled variable references.
* Class variables will have special resolution rules.
* In that case, we call their "resolver" procs to get our
* hands on the variable, and we make the compiled local a
* link to the real variable.
*/
 
framePtr->procPtr = procPtr;
framePtr->numCompiledLocals = localCt;
framePtr->compiledLocals = contextPtr->compiledLocals;
 
TclInitCompiledLocals(interp, framePtr,
(Namespace*)contextClass->namesp);
}
return result;
}
 
/*
* ------------------------------------------------------------------------
* Itcl_PopContext()
*
* Removes a class/object context previously set up by Itcl_PushContext.
* Usually called after an [incr Tcl] code body has been executed,
* to clean up.
* ------------------------------------------------------------------------
*/
void
Itcl_PopContext(interp, contextPtr)
Tcl_Interp *interp; /* interpreter managing this body of code */
ItclContext *contextPtr; /* storage space for class/object context */
{
Tcl_CallFrame *framePtr;
ItclObjectInfo *info;
ItclObject *contextObj;
Tcl_HashEntry *entry;
 
/*
* See if the current call frame has an object context
* associated with it. If so, release the claim on the
* object info.
*/
framePtr = _Tcl_GetCallFrame(interp, 0);
info = contextPtr->classDefn->info;
 
entry = Tcl_FindHashEntry(&info->contextFrames, (char*)framePtr);
if (entry != NULL) {
contextObj = (ItclObject*)Tcl_GetHashValue(entry);
Itcl_ReleaseData((ClientData)contextObj);
Tcl_DeleteHashEntry(entry);
}
 
/*
* Remove the call frame.
*/
Tcl_PopCallFrame(interp);
 
/*
* Free the compiledLocals array if malloc'ed storage was used.
*/
if (contextPtr->compiledLocals != &contextPtr->localStorage[0]) {
ckfree((char*)contextPtr->compiledLocals);
}
}
 
/*
* ------------------------------------------------------------------------
* Itcl_GetContext()
*
* Convenience routine for looking up the current object/class context.
* Useful in implementing methods/procs to see what class, and perhaps
* what object, is active.
*
* Returns TCL_OK if the current namespace is a class namespace.
* Also returns pointers to the class definition, and to object
* data if an object context is active. Returns TCL_ERROR (along
* with an error message in the interpreter) if a class namespace
* is not active.
* ------------------------------------------------------------------------
*/
int
Itcl_GetContext(interp, cdefnPtr, odefnPtr)
Tcl_Interp *interp; /* current interpreter */
ItclClass **cdefnPtr; /* returns: class definition or NULL */
ItclObject **odefnPtr; /* returns: object data or NULL */
{
Tcl_Namespace *activeNs = Tcl_GetCurrentNamespace(interp);
ItclObjectInfo *info;
Tcl_CallFrame *framePtr;
Tcl_HashEntry *entry;
 
/*
* Return NULL for anything that cannot be found.
*/
*cdefnPtr = NULL;
*odefnPtr = NULL;
 
/*
* If the active namespace is a class namespace, then return
* all known info. See if the current call frame is a known
* object context, and if so, return that context.
*/
if (Itcl_IsClassNamespace(activeNs)) {
*cdefnPtr = (ItclClass*)activeNs->clientData;
 
framePtr = _Tcl_GetCallFrame(interp, 0);
 
info = (*cdefnPtr)->info;
entry = Tcl_FindHashEntry(&info->contextFrames, (char*)framePtr);
 
if (entry != NULL) {
*odefnPtr = (ItclObject*)Tcl_GetHashValue(entry);
}
return TCL_OK;
}
 
/*
* If there is no class/object context, return an error message.
*/
Tcl_AppendStringsToObj(Tcl_GetObjResult(interp),
"namespace \"", activeNs->fullName, "\" is not a class namespace",
(char*)NULL);
 
return TCL_ERROR;
}
 
/*
* ------------------------------------------------------------------------
* Itcl_AssignArgs()
*
* Matches a list of arguments against a Tcl argument specification.
* Supports all of the rules regarding arguments for Tcl procs, including
* default arguments and variable-length argument lists.
*
* Assumes that a local call frame is already installed. As variables
* are successfully matched, they are stored as variables in the call
* frame. Returns TCL_OK on success, or TCL_ERROR (along with an error
* message in interp->result) on error.
* ------------------------------------------------------------------------
*/
int
Itcl_AssignArgs(interp, objc, objv, mfunc)
Tcl_Interp *interp; /* interpreter */
int objc; /* number of arguments */
Tcl_Obj *CONST objv[]; /* argument objects */
ItclMemberFunc *mfunc; /* member function info (for error messages) */
{
ItclMemberCode *mcode = mfunc->member->code;
 
int result = TCL_OK;
 
int defargc;
char **defargv = NULL;
Tcl_Obj **defobjv = NULL;
int configc = 0;
ItclVarDefn **configVars = NULL;
char **configVals = NULL;
 
int vi, argsLeft;
ItclClass *contextClass;
ItclObject *contextObj;
CompiledLocal *argPtr;
CallFrame *framePtr;
Var *varPtr;
Tcl_Obj *objPtr, *listPtr;
char *value;
 
framePtr = (CallFrame*) _Tcl_GetCallFrame(interp, 0);
framePtr->objc = objc;
framePtr->objv = objv; /* ref counts for args are incremented below */
 
/*
* See if there is a current object context. We may need
* it later on.
*/
(void) Itcl_GetContext(interp, &contextClass, &contextObj);
Tcl_ResetResult(interp);
 
/*
* Match the actual arguments against the procedure's formal
* parameters to compute local variables.
*/
varPtr = framePtr->compiledLocals;
 
for (argsLeft=mcode->argcount, argPtr=mcode->arglist, objv++, objc--;
argsLeft > 0;
argPtr=argPtr->nextPtr, argsLeft--, varPtr++, objv++, objc--)
{
if (!TclIsVarArgument(argPtr)) {
panic("local variable %s is not argument but should be",
argPtr->name);
return TCL_ERROR;
}
if (TclIsVarTemporary(argPtr)) {
panic("local variable is temporary but should be an argument");
return TCL_ERROR;
}
 
/*
* Handle the special case of the last formal being "args".
* When it occurs, assign it a list consisting of all the
* remaining actual arguments.
*/
if ((argsLeft == 1) && (strcmp(argPtr->name, "args") == 0)) {
if (objc < 0) objc = 0;
 
listPtr = Tcl_NewListObj(objc, objv);
varPtr->value.objPtr = listPtr;
Tcl_IncrRefCount(listPtr); /* local var is a reference */
varPtr->flags &= ~VAR_UNDEFINED;
objc = 0;
 
break;
}
 
/*
* Handle the special case of the last formal being "config".
* When it occurs, treat all remaining arguments as public
* variable assignments. Set the local "config" variable
* to the list of public variables assigned.
*/
else if ( (argsLeft == 1) &&
(strcmp(argPtr->name, "config") == 0) &&
contextObj )
{
/*
* If this is not an old-style method, discourage against
* the use of the "config" argument.
*/
if ((mfunc->member->flags & ITCL_OLD_STYLE) == 0) {
Tcl_AppendStringsToObj(Tcl_GetObjResult(interp),
"\"config\" argument is an anachronism\n",
"[incr Tcl] no longer supports the \"config\" argument.\n",
"Instead, use the \"args\" argument and then use the\n",
"built-in configure method to handle args like this:\n",
" eval configure $args",
(char*)NULL);
result = TCL_ERROR;
goto argErrors;
}
 
/*
* Otherwise, handle the "config" argument in the usual way...
* - parse all "-name value" assignments
* - set "config" argument to the list of variable names
*/
if (objc > 0) { /* still have some arguments left? */
 
result = ItclParseConfig(interp, objc, objv, contextObj,
&configc, &configVars, &configVals);
 
if (result != TCL_OK) {
goto argErrors;
}
 
listPtr = Tcl_NewListObj(0, (Tcl_Obj**)NULL);
for (vi=0; vi < configc; vi++) {
objPtr = Tcl_NewStringObj(
configVars[vi]->member->classDefn->name, -1);
Tcl_AppendToObj(objPtr, "::", -1);
Tcl_AppendToObj(objPtr, configVars[vi]->member->name, -1);
 
Tcl_ListObjAppendElement(interp, listPtr, objPtr);
}
 
varPtr->value.objPtr = listPtr;
Tcl_IncrRefCount(listPtr); /* local var is a reference */
varPtr->flags &= ~VAR_UNDEFINED;
 
objc = 0; /* all remaining args handled */
}
 
else if (argPtr->defValuePtr) {
value = Tcl_GetStringFromObj(argPtr->defValuePtr, (int*)NULL);
 
result = Tcl_SplitList(interp, value, &defargc, &defargv);
if (result != TCL_OK) {
goto argErrors;
}
defobjv = (Tcl_Obj**)ckalloc(
(unsigned)(defargc*sizeof(Tcl_Obj*))
);
for (vi=0; vi < defargc; vi++) {
objPtr = Tcl_NewStringObj(defargv[vi], -1);
Tcl_IncrRefCount(objPtr);
defobjv[vi] = objPtr;
}
 
result = ItclParseConfig(interp, defargc, defobjv, contextObj,
&configc, &configVars, &configVals);
 
if (result != TCL_OK) {
goto argErrors;
}
 
listPtr = Tcl_NewListObj(0, (Tcl_Obj**)NULL);
for (vi=0; vi < configc; vi++) {
objPtr = Tcl_NewStringObj(
configVars[vi]->member->classDefn->name, -1);
Tcl_AppendToObj(objPtr, "::", -1);
Tcl_AppendToObj(objPtr, configVars[vi]->member->name, -1);
 
Tcl_ListObjAppendElement(interp, listPtr, objPtr);
}
 
varPtr->value.objPtr = listPtr;
Tcl_IncrRefCount(listPtr); /* local var is a reference */
varPtr->flags &= ~VAR_UNDEFINED;
}
else {
objPtr = Tcl_NewStringObj("", 0);
varPtr->value.objPtr = objPtr;
Tcl_IncrRefCount(objPtr); /* local var is a reference */
varPtr->flags &= ~VAR_UNDEFINED;
}
}
 
/*
* Resume the usual processing of arguments...
*/
else if (objc > 0) { /* take next arg as value */
objPtr = *objv;
varPtr->value.objPtr = objPtr;
varPtr->flags &= ~VAR_UNDEFINED;
Tcl_IncrRefCount(objPtr); /* local var is a reference */
}
else if (argPtr->defValuePtr) { /* ...or use default value */
objPtr = argPtr->defValuePtr;
varPtr->value.objPtr = objPtr;
varPtr->flags &= ~VAR_UNDEFINED;
Tcl_IncrRefCount(objPtr); /* local var is a reference */
}
else {
if (mfunc) {
objPtr = Tcl_GetObjResult(interp);
Tcl_AppendToObj(objPtr, "wrong # args: should be \"", -1);
Itcl_GetMemberFuncUsage(mfunc, contextObj, objPtr);
Tcl_AppendToObj(objPtr, "\"", -1);
} else {
Tcl_AppendStringsToObj(Tcl_GetObjResult(interp),
"no value given for parameter \"", argPtr->name, "\"",
(char*)NULL);
}
result = TCL_ERROR;
goto argErrors;
}
}
 
if (objc > 0) {
if (mfunc) {
objPtr = Tcl_GetObjResult(interp);
Tcl_AppendToObj(objPtr, "wrong # args: should be \"", -1);
Itcl_GetMemberFuncUsage(mfunc, contextObj, objPtr);
Tcl_AppendToObj(objPtr, "\"", -1);
} else {
Tcl_AppendStringsToObj(Tcl_GetObjResult(interp),
"too many arguments",
(char*)NULL);
}
result = TCL_ERROR;
goto argErrors;
}
 
/*
* Handle any "config" assignments.
*/
if (configc > 0) {
if (ItclHandleConfig(interp, configc, configVars, configVals,
contextObj) != TCL_OK) {
 
result = TCL_ERROR;
goto argErrors;
}
}
 
/*
* All arguments were successfully matched.
*/
result = TCL_OK;
 
/*
* If any errors were found, clean up and return error status.
*/
argErrors:
if (defobjv) {
for (vi=0; vi < defargc; vi++) {
Tcl_DecrRefCount(defobjv[vi]);
}
ckfree((char*)defobjv);
}
if (defargv) {
ckfree((char*)defargv);
}
if (configVars) {
ckfree((char*)configVars);
}
if (configVals) {
ckfree((char*)configVals);
}
return result;
}
 
/*
* ------------------------------------------------------------------------
* ItclParseConfig()
*
* Parses a set of arguments as "-variable value" assignments.
* Interprets all variable names in the most-specific class scope,
* so that an inherited method with a "config" parameter will work
* correctly. Returns a list of public variable names and their
* corresponding values; both lists should passed to ItclHandleConfig()
* to perform assignments, and freed when no longer in use. Returns a
* status TCL_OK/TCL_ERROR and returns error messages in the interpreter.
* ------------------------------------------------------------------------
*/
static int
ItclParseConfig(interp, objc, objv, contextObj, rargc, rvars, rvals)
Tcl_Interp *interp; /* interpreter */
int objc; /* number of arguments */
Tcl_Obj *CONST objv[]; /* argument objects */
ItclObject *contextObj; /* object whose public vars are being config'd */
int *rargc; /* return: number of variables accessed */
ItclVarDefn ***rvars; /* return: list of variables */
char ***rvals; /* return: list of values */
{
int result = TCL_OK;
ItclVarLookup *vlookup;
Tcl_HashEntry *entry;
char *varName, *value;
 
if (objc < 0) objc = 0;
*rargc = 0;
*rvars = (ItclVarDefn**)ckalloc((unsigned)(objc*sizeof(ItclVarDefn*)));
*rvals = (char**)ckalloc((unsigned)(objc*sizeof(char*)));
 
while (objc-- > 0) {
/*
* Next argument should be "-variable"
*/
varName = Tcl_GetStringFromObj(*objv, (int*)NULL);
if (*varName != '-') {
Tcl_AppendStringsToObj(Tcl_GetObjResult(interp),
"syntax error in config assignment \"",
varName, "\": should be \"-variable value\"",
(char*)NULL);
result = TCL_ERROR;
break;
}
else if (objc-- <= 0) {
Tcl_AppendStringsToObj(Tcl_GetObjResult(interp),
"syntax error in config assignment \"",
varName, "\": should be \"-variable value\" (missing value)",
(char*)NULL);
result = TCL_ERROR;
break;
}
 
entry = Tcl_FindHashEntry(&contextObj->classDefn->resolveVars,
varName+1);
 
if (entry) {
vlookup = (ItclVarLookup*)Tcl_GetHashValue(entry);
value = Tcl_GetStringFromObj(*(objv+1), (int*)NULL);
 
(*rvars)[*rargc] = vlookup->vdefn; /* variable definition */
(*rvals)[*rargc] = value; /* config value */
(*rargc)++;
objv += 2;
}
else {
Tcl_AppendStringsToObj(Tcl_GetObjResult(interp),
"syntax error in config assignment \"",
varName, "\": unrecognized variable",
(char*)NULL);
result = TCL_ERROR;
break;
}
}
return result;
}
/*
* ------------------------------------------------------------------------
* ItclHandleConfig()
*
* Handles the assignment of "config" values to public variables.
* The list of assignments is parsed in ItclParseConfig(), but the
* actual assignments are performed here. If the variables have any
* associated "config" code, it is invoked here as well. If errors
* are detected during assignment or "config" code execution, the
* variable is set back to its previous value and an error is returned.
*
* Returns a status TCL_OK/TCL_ERROR, and returns any error messages
* in the given interpreter.
* ------------------------------------------------------------------------
*/
static int
ItclHandleConfig(interp, argc, vars, vals, contextObj)
Tcl_Interp *interp; /* interpreter currently in control */
int argc; /* number of assignments */
ItclVarDefn **vars; /* list of public variable definitions */
char **vals; /* list of public variable values */
ItclObject *contextObj; /* object whose public vars are being config'd */
{
int result = TCL_OK;
 
int i;
char *val;
Tcl_DString lastval;
ItclContext context;
Tcl_CallFrame *oldFramePtr, *uplevelFramePtr;
 
Tcl_DStringInit(&lastval);
 
/*
* All "config" assignments are performed in the most-specific
* class scope, so that inherited methods with "config" arguments
* will work correctly.
*/
result = Itcl_PushContext(interp, (ItclMember*)NULL,
contextObj->classDefn, contextObj, &context);
 
if (result != TCL_OK) {
return TCL_ERROR;
}
 
/*
* Perform each assignment and execute the "config" code
* associated with each variable. If any errors are encountered,
* set the variable back to its previous value, and return an error.
*/
for (i=0; i < argc; i++) {
val = Tcl_GetVar2(interp, vars[i]->member->fullname, (char*)NULL, 0);
if (!val) {
val = "";
}
Tcl_DStringSetLength(&lastval, 0);
Tcl_DStringAppend(&lastval, val, -1);
 
/*
* Set the variable to the specified value.
*/
if (!Tcl_SetVar2(interp, vars[i]->member->fullname, (char*)NULL,
vals[i], 0)) {
 
char msg[256];
sprintf(msg, "\n (while configuring public variable \"%.100s\")", vars[i]->member->fullname);
Tcl_AddErrorInfo(interp, msg);
result = TCL_ERROR;
break;
}
 
/*
* If the variable has a "config" condition, then execute it.
* If it fails, put the variable back the way it was and return
* an error.
*
* TRICKY NOTE: Be careful to evaluate the code one level
* up in the call stack, so that it's executed in the
* calling context, and not in the context that we've
* set up for public variable access.
*/
if (vars[i]->member->code) {
 
uplevelFramePtr = _Tcl_GetCallFrame(interp, 1);
oldFramePtr = _Tcl_ActivateCallFrame(interp, uplevelFramePtr);
 
result = Itcl_EvalMemberCode(interp, (ItclMemberFunc*)NULL,
vars[i]->member, contextObj, 0, (Tcl_Obj* CONST*)NULL);
 
(void) _Tcl_ActivateCallFrame(interp, oldFramePtr);
 
if (result != TCL_OK) {
char msg[256];
sprintf(msg, "\n (while configuring public variable \"%.100s\")", vars[i]->member->fullname);
Tcl_AddErrorInfo(interp, msg);
Tcl_SetVar2(interp, vars[i]->member->fullname, (char*)NULL,
Tcl_DStringValue(&lastval), 0);
break;
}
}
}
 
/*
* Clean up and return.
*/
Itcl_PopContext(interp, &context);
Tcl_DStringFree(&lastval);
 
return result;
}
 
/*
* ------------------------------------------------------------------------
* Itcl_ConstructBase()
*
* Usually invoked just before executing the body of a constructor
* when an object is first created. This procedure makes sure that
* all base classes are properly constructed. If an "initCode" fragment
* was defined with the constructor for the class, then it is invoked.
* After that, the list of base classes is checked for constructors
* that are defined but have not yet been invoked. Each of these is
* invoked implicitly with no arguments.
*
* Assumes that a local call frame is already installed, and that
* constructor arguments have already been matched and are sitting in
* this frame. Returns TCL_OK on success; otherwise, this procedure
* returns TCL_ERROR, along with an error message in the interpreter.
* ------------------------------------------------------------------------
*/
int
Itcl_ConstructBase(interp, contextObj, contextClass)
Tcl_Interp *interp; /* interpreter */
ItclObject *contextObj; /* object being constructed */
ItclClass *contextClass; /* current class being constructed */
{
int result;
Itcl_ListElem *elem;
ItclClass *cdefn;
Tcl_HashEntry *entry;
 
/*
* If the class has an "initCode", invoke it in the current context.
*
* TRICKY NOTE:
* This context is the call frame containing the arguments
* for the constructor. The "initCode" makes sense right
* now--just before the body of the constructor is executed.
*/
if (contextClass->initCode) {
/* CYGNUS LOCAL - Fix for Tcl8.1 */
#if TCL_MAJOR_VERSION == 8 && TCL_MINOR_VERSION == 0
if (Tcl_EvalObj(interp, contextClass->initCode) != TCL_OK) {
#else
if (Tcl_EvalObj(interp, contextClass->initCode, 0) != TCL_OK) {
#endif
/* END CYGNUS LOCAL */
return TCL_ERROR;
}
}
 
/*
* Scan through the list of base classes and see if any of these
* have not been constructed. Invoke base class constructors
* implicitly, as needed. Go through the list of base classes
* in reverse order, so that least-specific classes are constructed
* first.
*/
elem = Itcl_LastListElem(&contextClass->bases);
while (elem) {
cdefn = (ItclClass*)Itcl_GetListValue(elem);
 
if (!Tcl_FindHashEntry(contextObj->constructed, cdefn->name)) {
 
result = Itcl_InvokeMethodIfExists(interp, "constructor",
cdefn, contextObj, 0, (Tcl_Obj* CONST*)NULL);
 
if (result != TCL_OK) {
return TCL_ERROR;
}
 
/*
* The base class may not have a constructor, but its
* own base classes could have one. If the constructor
* wasn't found in the last step, then other base classes
* weren't constructed either. Make sure that all of its
* base classes are properly constructed.
*/
entry = Tcl_FindHashEntry(&cdefn->functions, "constructor");
if (entry == NULL) {
result = Itcl_ConstructBase(interp, contextObj, cdefn);
if (result != TCL_OK) {
return TCL_ERROR;
}
}
}
elem = Itcl_PrevListElem(elem);
}
return TCL_OK;
}
 
/*
* ------------------------------------------------------------------------
* Itcl_InvokeMethodIfExists()
*
* Looks for a particular method in the specified class. If the
* method is found, it is invoked with the given arguments. Any
* protection level (protected/private) for the method is ignored.
* If the method does not exist, this procedure does nothing.
*
* This procedure is used primarily to invoke the constructor/destructor
* when an object is created/destroyed.
*
* Returns TCL_OK on success; otherwise, this procedure returns
* TCL_ERROR along with an error message in the interpreter.
* ------------------------------------------------------------------------
*/
int
Itcl_InvokeMethodIfExists(interp, name, contextClass, contextObj, objc, objv)
Tcl_Interp *interp; /* interpreter */
char *name; /* name of desired method */
ItclClass *contextClass; /* current class being constructed */
ItclObject *contextObj; /* object being constructed */
int objc; /* number of arguments */
Tcl_Obj *CONST objv[]; /* argument objects */
{
int result = TCL_OK;
 
ItclMemberFunc *mfunc;
ItclMember *member;
Tcl_HashEntry *entry;
Tcl_Obj *cmdlinePtr;
int cmdlinec;
Tcl_Obj **cmdlinev;
 
/*
* Scan through the list of base classes and see if any of these
* have not been constructed. Invoke base class constructors
* implicitly, as needed. Go through the list of base classes
* in reverse order, so that least-specific classes are constructed
* first.
*/
entry = Tcl_FindHashEntry(&contextClass->functions, name);
 
if (entry) {
mfunc = (ItclMemberFunc*)Tcl_GetHashValue(entry);
member = mfunc->member;
 
/*
* Prepend the method name to the list of arguments.
*/
cmdlinePtr = Itcl_CreateArgs(interp, name, objc, objv);
 
(void) Tcl_ListObjGetElements((Tcl_Interp*)NULL, cmdlinePtr,
&cmdlinec, &cmdlinev);
 
/*
* Execute the code for the method. Be careful to protect
* the method in case it gets deleted during execution.
*/
Itcl_PreserveData((ClientData)mfunc);
 
result = Itcl_EvalMemberCode(interp, mfunc, member,
contextObj, cmdlinec, cmdlinev);
 
result = Itcl_ReportFuncErrors(interp, mfunc,
contextObj, result);
 
Itcl_ReleaseData((ClientData)mfunc);
Tcl_DecrRefCount(cmdlinePtr);
}
return result;
}
 
/*
* ------------------------------------------------------------------------
* Itcl_ReportFuncErrors()
*
* Used to interpret the status code returned when the body of a
* Tcl-style proc is executed. Handles the "errorInfo" and "errorCode"
* variables properly, and adds error information into the interpreter
* if anything went wrong. Returns a new status code that should be
* treated as the return status code for the command.
*
* This same operation is usually buried in the Tcl InterpProc()
* procedure. It is defined here so that it can be reused more easily.
* ------------------------------------------------------------------------
*/
int
Itcl_ReportFuncErrors(interp, mfunc, contextObj, result)
Tcl_Interp* interp; /* interpreter being modified */
ItclMemberFunc *mfunc; /* command member that was invoked */
ItclObject *contextObj; /* object context for this command */
int result; /* integer status code from proc body */
{
Interp* iPtr = (Interp*)interp;
Tcl_Obj *objPtr;
char num[20];
 
if (result != TCL_OK) {
if (result == TCL_RETURN) {
result = TclUpdateReturnInfo(iPtr);
}
else if (result == TCL_ERROR) {
objPtr = Tcl_NewStringObj("\n ", -1);
Tcl_IncrRefCount(objPtr);
 
if (mfunc->member->flags & ITCL_CONSTRUCTOR) {
Tcl_AppendToObj(objPtr, "while constructing object \"", -1);
Tcl_GetCommandFullName(contextObj->classDefn->interp,
contextObj->accessCmd, objPtr);
Tcl_AppendToObj(objPtr, "\" in ", -1);
Tcl_AppendToObj(objPtr, mfunc->member->fullname, -1);
if ((mfunc->member->code->flags & ITCL_IMPLEMENT_TCL) != 0) {
Tcl_AppendToObj(objPtr, " (", -1);
}
}
 
else if (mfunc->member->flags & ITCL_DESTRUCTOR) {
Tcl_AppendToObj(objPtr, "while deleting object \"", -1);
Tcl_GetCommandFullName(contextObj->classDefn->interp,
contextObj->accessCmd, objPtr);
Tcl_AppendToObj(objPtr, "\" in ", -1);
Tcl_AppendToObj(objPtr, mfunc->member->fullname, -1);
if ((mfunc->member->code->flags & ITCL_IMPLEMENT_TCL) != 0) {
Tcl_AppendToObj(objPtr, " (", -1);
}
}
 
else {
Tcl_AppendToObj(objPtr, "(", -1);
 
if (contextObj && contextObj->accessCmd) {
Tcl_AppendToObj(objPtr, "object \"", -1);
Tcl_GetCommandFullName(contextObj->classDefn->interp,
contextObj->accessCmd, objPtr);
Tcl_AppendToObj(objPtr, "\" ", -1);
}
 
if ((mfunc->member->flags & ITCL_COMMON) != 0) {
Tcl_AppendToObj(objPtr, "procedure", -1);
} else {
Tcl_AppendToObj(objPtr, "method", -1);
}
Tcl_AppendToObj(objPtr, " \"", -1);
Tcl_AppendToObj(objPtr, mfunc->member->fullname, -1);
Tcl_AppendToObj(objPtr, "\" ", -1);
}
 
if ((mfunc->member->code->flags & ITCL_IMPLEMENT_TCL) != 0) {
Tcl_AppendToObj(objPtr, "body line ", -1);
sprintf(num, "%d", iPtr->errorLine);
Tcl_AppendToObj(objPtr, num, -1);
Tcl_AppendToObj(objPtr, ")", -1);
} else {
Tcl_AppendToObj(objPtr, ")", -1);
}
 
Tcl_AddErrorInfo(interp, Tcl_GetStringFromObj(objPtr, (int*)NULL));
Tcl_DecrRefCount(objPtr);
}
 
else if (result == TCL_BREAK) {
Tcl_ResetResult(interp);
Tcl_AppendToObj(Tcl_GetObjResult(interp),
"invoked \"break\" outside of a loop", -1);
result = TCL_ERROR;
}
 
else if (result == TCL_CONTINUE) {
Tcl_ResetResult(interp);
Tcl_AppendToObj(Tcl_GetObjResult(interp),
"invoked \"continue\" outside of a loop", -1);
result = TCL_ERROR;
}
}
return result;
}
itcl_methods.c Property changes : Added: svn:executable ## -0,0 +1 ## +* \ No newline at end of property Index: itcl.h =================================================================== --- itcl.h (nonexistent) +++ itcl.h (revision 1765) @@ -0,0 +1,188 @@ +/* + * ------------------------------------------------------------------------ + * PACKAGE: [incr Tcl] + * DESCRIPTION: Object-Oriented Extensions to Tcl + * + * [incr Tcl] provides object-oriented extensions to Tcl, much as + * C++ provides object-oriented extensions to C. It provides a means + * of encapsulating related procedures together with their shared data + * in a local namespace that is hidden from the outside world. It + * promotes code re-use through inheritance. More than anything else, + * it encourages better organization of Tcl applications through the + * object-oriented paradigm, leading to code that is easier to + * understand and maintain. + * + * ADDING [incr Tcl] TO A Tcl-BASED APPLICATION: + * + * To add [incr Tcl] facilities to a Tcl application, modify the + * Tcl_AppInit() routine as follows: + * + * 1) Include this header file near the top of the file containing + * Tcl_AppInit(): + * + * #include "itcl.h" + * + * 2) Within the body of Tcl_AppInit(), add the following lines: + * + * if (Itcl_Init(interp) == TCL_ERROR) { + * return TCL_ERROR; + * } + * + * 3) Link your application with libitcl.a + * + * NOTE: An example file "tclAppInit.c" containing the changes shown + * above is included in this distribution. + * + * ======================================================================== + * AUTHOR: Michael J. McLennan + * Bell Labs Innovations for Lucent Technologies + * mmclennan@lucent.com + * http://www.tcltk.com/itcl + * + * RCS: $Id: itcl.h,v 1.1.1.1 2002-01-16 10:24:46 markom Exp $ + * ======================================================================== + * Copyright (c) 1993-1998 Lucent Technologies, Inc. + * ------------------------------------------------------------------------ + * See the file "license.terms" for information on usage and redistribution + * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + */ +#ifndef ITCL_H +#define ITCL_H + +#include "tcl.h" + +#define ITCL_VERSION "3.0" +#define ITCL_PATCH_LEVEL "3.0" +#define ITCL_MAJOR_VERSION 3 +#define ITCL_MINOR_VERSION 0 +#define ITCL_RELEASE_LEVEL 0 + +/* + * A special definition used to allow this header file to be included + * in resource files so that they can get obtain version information from + * this file. Resource compilers don't like all the C stuff, like typedefs + * and procedure declarations, that occur below. + */ + +#ifndef RESOURCE_INCLUDED + +#include "tclInt.h" + +#ifdef BUILD_itcl +# undef TCL_STORAGE_CLASS +# define TCL_STORAGE_CLASS DLLEXPORT +#endif + +/* + * Protection levels: + * + * ITCL_PUBLIC - accessible from any namespace + * ITCL_PROTECTED - accessible from namespace that imports in "protected" mode + * ITCL_PRIVATE - accessible only within the namespace that contains it + */ +#define ITCL_PUBLIC 1 +#define ITCL_PROTECTED 2 +#define ITCL_PRIVATE 3 +#define ITCL_DEFAULT_PROTECT 4 + + +/* + * Generic stack. + */ +typedef struct Itcl_Stack { + ClientData *values; /* values on stack */ + int len; /* number of values on stack */ + int max; /* maximum size of stack */ + ClientData space[5]; /* initial space for stack data */ +} Itcl_Stack; + +#define Itcl_GetStackSize(stackPtr) ((stackPtr)->len) + +/* + * Generic linked list. + */ +struct Itcl_List; +typedef struct Itcl_ListElem { + struct Itcl_List* owner; /* list containing this element */ + ClientData value; /* value associated with this element */ + struct Itcl_ListElem *prev; /* previous element in linked list */ + struct Itcl_ListElem *next; /* next element in linked list */ +} Itcl_ListElem; + +typedef struct Itcl_List { + int validate; /* validation stamp */ + int num; /* number of elements */ + struct Itcl_ListElem *head; /* previous element in linked list */ + struct Itcl_ListElem *tail; /* next element in linked list */ +} Itcl_List; + +#define Itcl_FirstListElem(listPtr) ((listPtr)->head) +#define Itcl_LastListElem(listPtr) ((listPtr)->tail) +#define Itcl_NextListElem(elemPtr) ((elemPtr)->next) +#define Itcl_PrevListElem(elemPtr) ((elemPtr)->prev) +#define Itcl_GetListLength(listPtr) ((listPtr)->num) +#define Itcl_GetListValue(elemPtr) ((elemPtr)->value) + +/* + * Token representing the state of an interpreter. + */ +typedef struct Itcl_InterpState_ *Itcl_InterpState; + + +/* + * Exported functions + */ +EXTERN int Itcl_Init _ANSI_ARGS_((Tcl_Interp *interp)); +EXTERN int Itcl_SafeInit _ANSI_ARGS_((Tcl_Interp *interp)); + +EXTERN int Itcl_RegisterC _ANSI_ARGS_((Tcl_Interp *interp, + char *name, Tcl_CmdProc *proc, ClientData clientData, + Tcl_CmdDeleteProc *deleteProc)); +EXTERN int Itcl_RegisterObjC _ANSI_ARGS_((Tcl_Interp *interp, + char *name, Tcl_ObjCmdProc *proc, ClientData clientData, + Tcl_CmdDeleteProc *deleteProc)); +EXTERN int Itcl_FindC _ANSI_ARGS_((Tcl_Interp *interp, char *name, + Tcl_CmdProc **argProcPtr, Tcl_ObjCmdProc **objProcPtr, + ClientData *cDataPtr)); + +EXTERN void Itcl_InitStack _ANSI_ARGS_((Itcl_Stack *stack)); +EXTERN void Itcl_DeleteStack _ANSI_ARGS_((Itcl_Stack *stack)); +EXTERN void Itcl_PushStack _ANSI_ARGS_((ClientData cdata, + Itcl_Stack *stack)); +EXTERN ClientData Itcl_PopStack _ANSI_ARGS_((Itcl_Stack *stack)); +EXTERN ClientData Itcl_PeekStack _ANSI_ARGS_((Itcl_Stack *stack)); +EXTERN ClientData Itcl_GetStackValue _ANSI_ARGS_((Itcl_Stack *stack, + int pos)); + +EXTERN void Itcl_InitList _ANSI_ARGS_((Itcl_List *listPtr)); +EXTERN void Itcl_DeleteList _ANSI_ARGS_((Itcl_List *listPtr)); +EXTERN Itcl_ListElem* Itcl_CreateListElem _ANSI_ARGS_((Itcl_List *listPtr)); +EXTERN Itcl_ListElem* Itcl_DeleteListElem _ANSI_ARGS_((Itcl_ListElem *elemPtr)); +EXTERN Itcl_ListElem* Itcl_InsertList _ANSI_ARGS_((Itcl_List *listPtr, + ClientData val)); +EXTERN Itcl_ListElem* Itcl_InsertListElem _ANSI_ARGS_((Itcl_ListElem *pos, + ClientData val)); +EXTERN Itcl_ListElem* Itcl_AppendList _ANSI_ARGS_((Itcl_List *listPtr, + ClientData val)); +EXTERN Itcl_ListElem* Itcl_AppendListElem _ANSI_ARGS_((Itcl_ListElem *pos, + ClientData val)); +EXTERN void Itcl_SetListValue _ANSI_ARGS_((Itcl_ListElem *elemPtr, + ClientData val)); + +EXTERN void Itcl_EventuallyFree _ANSI_ARGS_((ClientData cdata, + Tcl_FreeProc *fproc)); +EXTERN void Itcl_PreserveData _ANSI_ARGS_((ClientData cdata)); +EXTERN void Itcl_ReleaseData _ANSI_ARGS_((ClientData cdata)); + +EXTERN Itcl_InterpState Itcl_SaveInterpState _ANSI_ARGS_((Tcl_Interp* interp, + int status)); +EXTERN int Itcl_RestoreInterpState _ANSI_ARGS_((Tcl_Interp* interp, + Itcl_InterpState state)); +EXTERN void Itcl_DiscardInterpState _ANSI_ARGS_((Itcl_InterpState state)); + +#endif /* RESOURCE_INCLUDED */ + +#undef TCL_STORAGE_CLASS +#define TCL_STORAGE_CLASS DLLIMPORT + +#endif /* ITCL_H */
itcl.h Property changes : Added: svn:executable ## -0,0 +1 ## +* \ No newline at end of property Index: itcl_class.c =================================================================== --- itcl_class.c (nonexistent) +++ itcl_class.c (revision 1765) @@ -0,0 +1,1728 @@ +/* + * ------------------------------------------------------------------------ + * PACKAGE: [incr Tcl] + * DESCRIPTION: Object-Oriented Extensions to Tcl + * + * [incr Tcl] provides object-oriented extensions to Tcl, much as + * C++ provides object-oriented extensions to C. It provides a means + * of encapsulating related procedures together with their shared data + * in a local namespace that is hidden from the outside world. It + * promotes code re-use through inheritance. More than anything else, + * it encourages better organization of Tcl applications through the + * object-oriented paradigm, leading to code that is easier to + * understand and maintain. + * + * These procedures handle class definitions. Classes are composed of + * data members (public/protected/common) and the member functions + * (methods/procs) that operate on them. Each class has its own + * namespace which manages the class scope. + * + * ======================================================================== + * AUTHOR: Michael J. McLennan + * Bell Labs Innovations for Lucent Technologies + * mmclennan@lucent.com + * http://www.tcltk.com/itcl + * + * RCS: $Id: itcl_class.c,v 1.1.1.1 2002-01-16 10:24:46 markom Exp $ + * ======================================================================== + * Copyright (c) 1993-1998 Lucent Technologies, Inc. + * ------------------------------------------------------------------------ + * See the file "license.terms" for information on usage and redistribution + * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + */ +#include "itclInt.h" + +/* + * This structure is a subclass of Tcl_ResolvedVarInfo that contains the + * ItclVarLookup info needed at runtime. + */ +typedef struct ItclResolvedVarInfo { + Tcl_ResolvedVarInfo vinfo; /* This must be the first element. */ + ItclVarLookup *vlookup; /* Pointer to lookup info. */ +} ItclResolvedVarInfo; + +/* + * FORWARD DECLARATIONS + */ +static void ItclDestroyClass _ANSI_ARGS_((ClientData cdata)); +static void ItclDestroyClassNamesp _ANSI_ARGS_((ClientData cdata)); +static void ItclFreeClass _ANSI_ARGS_((char* cdata)); + +static Tcl_Var ItclClassRuntimeVarResolver _ANSI_ARGS_(( + Tcl_Interp *interp, Tcl_ResolvedVarInfo *vinfoPtr)); + + +/* + * ------------------------------------------------------------------------ + * Itcl_CreateClass() + * + * Creates a namespace and its associated class definition data. + * If a namespace already exists with that name, then this routine + * returns TCL_ERROR, along with an error message in the interp. + * If successful, it returns TCL_OK and a pointer to the new class + * definition. + * ------------------------------------------------------------------------ + */ +int +Itcl_CreateClass(interp, path, info, rPtr) + Tcl_Interp* interp; /* interpreter that will contain new class */ + char* path; /* name of new class */ + ItclObjectInfo *info; /* info for all known objects */ + ItclClass **rPtr; /* returns: pointer to class definition */ +{ + char *head, *tail; + Tcl_DString buffer; + Tcl_Command cmd; + Tcl_Namespace *classNs; + ItclClass *cdPtr; + ItclVarDefn *vdefn; + Tcl_HashEntry *entry; + int newEntry; + + /* + * Make sure that a class with the given name does not + * already exist in the current namespace context. If a + * namespace exists, that's okay. It may have been created + * to contain stubs during a "namespace import" operation. + * We'll just replace the namespace data below with the + * proper class data. + */ + classNs = Tcl_FindNamespace(interp, path, (Tcl_Namespace*)NULL, + /* flags */ 0); + + if (classNs != NULL && Itcl_IsClassNamespace(classNs)) { + Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), + "class \"", path, "\" already exists", + (char*)NULL); + return TCL_ERROR; + } + + /* + * Make sure that a command with the given class name does not + * already exist in the current namespace. This prevents the + * usual Tcl commands from being clobbered when a programmer + * makes a bogus call like "class info". + */ + cmd = Tcl_FindCommand(interp, path, (Tcl_Namespace*)NULL, + /* flags */ TCL_NAMESPACE_ONLY); + + if (cmd != NULL && !Itcl_IsStub(cmd)) { + Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), + "command \"", path, "\" already exists", + (char*)NULL); + + if (strstr(path,"::") == NULL) { + Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), + " in namespace \"", + Tcl_GetCurrentNamespace(interp)->fullName, "\"", + (char*)NULL); + } + return TCL_ERROR; + } + + /* + * Make sure that the class name does not have any goofy + * characters: + * + * . => reserved for member access like: class.publicVar + */ + Itcl_ParseNamespPath(path, &buffer, &head, &tail); + + if (strstr(tail,".")) { + Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), + "bad class name \"", tail, "\"", + (char*)NULL); + Tcl_DStringFree(&buffer); + return TCL_ERROR; + } + Tcl_DStringFree(&buffer); + + /* + * Allocate class definition data. + */ + cdPtr = (ItclClass*)ckalloc(sizeof(ItclClass)); + cdPtr->name = NULL; + cdPtr->fullname = NULL; + cdPtr->interp = interp; + cdPtr->info = info; Itcl_PreserveData((ClientData)info); + cdPtr->namesp = NULL; + cdPtr->accessCmd = NULL; + + Tcl_InitHashTable(&cdPtr->variables, TCL_STRING_KEYS); + Tcl_InitHashTable(&cdPtr->functions, TCL_STRING_KEYS); + + cdPtr->numInstanceVars = 0; + Tcl_InitHashTable(&cdPtr->resolveVars, TCL_STRING_KEYS); + Tcl_InitHashTable(&cdPtr->resolveCmds, TCL_STRING_KEYS); + + Itcl_InitList(&cdPtr->bases); + Itcl_InitList(&cdPtr->derived); + + cdPtr->initCode = NULL; + cdPtr->unique = 0; + cdPtr->flags = 0; + + /* + * Initialize the heritage info--each class starts with its + * own class definition in the heritage. Base classes are + * added to the heritage from the "inherit" statement. + */ + Tcl_InitHashTable(&cdPtr->heritage, TCL_ONE_WORD_KEYS); + (void) Tcl_CreateHashEntry(&cdPtr->heritage, (char*)cdPtr, &newEntry); + + /* + * Create a namespace to represent the class. Add the class + * definition info as client data for the namespace. If the + * namespace already exists, then replace any existing client + * data with the class data. + */ + Itcl_PreserveData((ClientData)cdPtr); + + if (classNs == NULL) { + classNs = Tcl_CreateNamespace(interp, path, + (ClientData)cdPtr, ItclDestroyClassNamesp); + } + else { + if (classNs->clientData && classNs->deleteProc) { + (*classNs->deleteProc)(classNs->clientData); + } + classNs->clientData = (ClientData)cdPtr; + classNs->deleteProc = ItclDestroyClassNamesp; + } + + Itcl_EventuallyFree((ClientData)cdPtr, ItclFreeClass); + + if (classNs == NULL) { + Itcl_ReleaseData((ClientData)cdPtr); + return TCL_ERROR; + } + + cdPtr->namesp = classNs; + + cdPtr->name = (char*)ckalloc((unsigned)(strlen(classNs->name)+1)); + strcpy(cdPtr->name, classNs->name); + + cdPtr->fullname = (char*)ckalloc((unsigned)(strlen(classNs->fullName)+1)); + strcpy(cdPtr->fullname, classNs->fullName); + + /* + * Add special name resolution procedures to the class namespace + * so that members are accessed according to the rules for + * [incr Tcl]. + */ + Tcl_SetNamespaceResolvers(classNs, Itcl_ClassCmdResolver, + Itcl_ClassVarResolver, Itcl_ClassCompiledVarResolver); + + /* + * Add the built-in "this" variable to the list of data members. + */ + (void) Itcl_CreateVarDefn(interp, cdPtr, "this", + (char*)NULL, (char*)NULL, &vdefn); + + vdefn->member->protection = ITCL_PROTECTED; /* always "protected" */ + vdefn->member->flags |= ITCL_THIS_VAR; /* mark as "this" variable */ + + entry = Tcl_CreateHashEntry(&cdPtr->variables, "this", &newEntry); + Tcl_SetHashValue(entry, (ClientData)vdefn); + + /* + * Create a command in the current namespace to manage the class: + * + * ?? + */ + Itcl_PreserveData((ClientData)cdPtr); + + cdPtr->accessCmd = Tcl_CreateObjCommand(interp, + cdPtr->fullname, Itcl_HandleClass, + (ClientData)cdPtr, ItclDestroyClass); + + *rPtr = cdPtr; + return TCL_OK; +} + + +/* + * ------------------------------------------------------------------------ + * Itcl_DeleteClass() + * + * Deletes a class by deleting all derived classes and all objects in + * that class, and finally, by destroying the class namespace. This + * procedure provides a friendly way of doing this. If any errors + * are detected along the way, the process is aborted. + * + * Returns TCL_OK if successful, or TCL_ERROR (along with an error + * message in the interpreter) if anything goes wrong. + * ------------------------------------------------------------------------ + */ +int +Itcl_DeleteClass(interp, cdefnPtr) + Tcl_Interp *interp; /* interpreter managing this class */ + ItclClass *cdefnPtr; /* class namespace */ +{ + ItclClass *cdPtr = NULL; + + Itcl_ListElem *elem; + ItclObject *contextObj; + Tcl_HashEntry *entry; + Tcl_HashSearch place; + Tcl_DString buffer; + + /* + * Destroy all derived classes, since these lose their meaning + * when the base class goes away. If anything goes wrong, + * abort with an error. + * + * TRICKY NOTE: When a derived class is destroyed, it + * automatically deletes itself from the "derived" list. + */ + elem = Itcl_FirstListElem(&cdefnPtr->derived); + while (elem) { + cdPtr = (ItclClass*)Itcl_GetListValue(elem); + elem = Itcl_NextListElem(elem); /* advance here--elem will go away */ + + if (Itcl_DeleteClass(interp, cdPtr) != TCL_OK) { + goto deleteClassFail; + } + } + + /* + * Scan through and find all objects that belong to this class. + * Note that more specialized objects have already been + * destroyed above, when derived classes were destroyed. + * Destroy objects and report any errors. + */ + entry = Tcl_FirstHashEntry(&cdefnPtr->info->objects, &place); + while (entry) { + contextObj = (ItclObject*)Tcl_GetHashValue(entry); + if (contextObj->classDefn == cdefnPtr) { + if (Itcl_DeleteObject(interp, contextObj) != TCL_OK) { + cdPtr = cdefnPtr; + goto deleteClassFail; + } + } + entry = Tcl_NextHashEntry(&place); + } + + /* + * Destroy the namespace associated with this class. + * + * TRICKY NOTE: + * The cleanup procedure associated with the namespace is + * invoked automatically. It does all of the same things + * above, but it also disconnects this class from its + * base-class lists, and removes the class access command. + */ + Tcl_DeleteNamespace(cdefnPtr->namesp); + return TCL_OK; + +deleteClassFail: + Tcl_DStringInit(&buffer); + Tcl_DStringAppend(&buffer, "\n (while deleting class \"", -1); + Tcl_DStringAppend(&buffer, cdPtr->namesp->fullName, -1); + Tcl_DStringAppend(&buffer, "\")", -1); + Tcl_AddErrorInfo(interp, Tcl_DStringValue(&buffer)); + Tcl_DStringFree(&buffer); + return TCL_ERROR; +} + + +/* + * ------------------------------------------------------------------------ + * ItclDestroyClass() + * + * Invoked whenever the access command for a class is destroyed. + * Destroys the namespace associated with the class, which also + * destroys all objects in the class and all derived classes. + * Disconnects this class from the "derived" class lists of its + * base classes, and releases any claim to the class definition + * data. If this is the last use of that data, the class will + * completely vanish at this point. + * ------------------------------------------------------------------------ + */ +static void +ItclDestroyClass(cdata) + ClientData cdata; /* class definition to be destroyed */ +{ + ItclClass *cdefnPtr = (ItclClass*)cdata; + cdefnPtr->accessCmd = NULL; + + Tcl_DeleteNamespace(cdefnPtr->namesp); + Itcl_ReleaseData((ClientData)cdefnPtr); +} + + +/* + * ------------------------------------------------------------------------ + * ItclDestroyClassNamesp() + * + * Invoked whenever the namespace associated with a class is destroyed. + * Destroys all objects associated with this class and all derived + * classes. Disconnects this class from the "derived" class lists + * of its base classes, and removes the class access command. Releases + * any claim to the class definition data. If this is the last use + * of that data, the class will completely vanish at this point. + * ------------------------------------------------------------------------ + */ +static void +ItclDestroyClassNamesp(cdata) + ClientData cdata; /* class definition to be destroyed */ +{ + ItclClass *cdefnPtr = (ItclClass*)cdata; + ItclObject *contextObj; + Itcl_ListElem *elem, *belem; + ItclClass *cdPtr, *basePtr, *derivedPtr; + Tcl_HashEntry *entry; + Tcl_HashSearch place; + + /* + * Destroy all derived classes, since these lose their meaning + * when the base class goes away. + * + * TRICKY NOTE: When a derived class is destroyed, it + * automatically deletes itself from the "derived" list. + */ + elem = Itcl_FirstListElem(&cdefnPtr->derived); + while (elem) { + cdPtr = (ItclClass*)Itcl_GetListValue(elem); + elem = Itcl_NextListElem(elem); /* advance here--elem will go away */ + + Tcl_DeleteNamespace(cdPtr->namesp); + } + + /* + * Scan through and find all objects that belong to this class. + * Destroy them quietly by deleting their access command. + */ + entry = Tcl_FirstHashEntry(&cdefnPtr->info->objects, &place); + while (entry) { + contextObj = (ItclObject*)Tcl_GetHashValue(entry); + if (contextObj->classDefn == cdefnPtr) { + Tcl_DeleteCommandFromToken(cdefnPtr->interp, contextObj->accessCmd); + } + entry = Tcl_NextHashEntry(&place); + } + + /* + * Next, remove this class from the "derived" list in + * all base classes. + */ + belem = Itcl_FirstListElem(&cdefnPtr->bases); + while (belem) { + basePtr = (ItclClass*)Itcl_GetListValue(belem); + + elem = Itcl_FirstListElem(&basePtr->derived); + while (elem) { + derivedPtr = (ItclClass*)Itcl_GetListValue(elem); + if (derivedPtr == cdefnPtr) { + Itcl_ReleaseData( Itcl_GetListValue(elem) ); + elem = Itcl_DeleteListElem(elem); + } else { + elem = Itcl_NextListElem(elem); + } + } + belem = Itcl_NextListElem(belem); + } + + /* + * Next, destroy the access command associated with the class. + */ + if (cdefnPtr->accessCmd) { + Command *cmdPtr = (Command*)cdefnPtr->accessCmd; + + cmdPtr->deleteProc = Itcl_ReleaseData; + Tcl_DeleteCommandFromToken(cdefnPtr->interp, cdefnPtr->accessCmd); + } + + /* + * Release the namespace's claim on the class definition. + */ + Itcl_ReleaseData((ClientData)cdefnPtr); +} + + +/* + * ------------------------------------------------------------------------ + * ItclFreeClass() + * + * Frees all memory associated with a class definition. This is + * usually invoked automatically by Itcl_ReleaseData(), when class + * data is no longer being used. + * ------------------------------------------------------------------------ + */ +static void +ItclFreeClass(cdata) + char *cdata; /* class definition to be destroyed */ +{ + ItclClass *cdefnPtr = (ItclClass*)cdata; + + int newEntry; + Itcl_ListElem *elem; + Tcl_HashSearch place; + Tcl_HashEntry *entry, *hPtr; + ItclVarDefn *vdefn; + ItclVarLookup *vlookup; + Var *varPtr; + Tcl_HashTable varTable; + + /* + * Tear down the list of derived classes. This list should + * really be empty if everything is working properly, but + * release it here just in case. + */ + elem = Itcl_FirstListElem(&cdefnPtr->derived); + while (elem) { + Itcl_ReleaseData( Itcl_GetListValue(elem) ); + elem = Itcl_NextListElem(elem); + } + Itcl_DeleteList(&cdefnPtr->derived); + + /* + * Tear down the variable resolution table. Some records + * appear multiple times in the table (for x, foo::x, etc.) + * so each one has a reference count. + */ + Tcl_InitHashTable(&varTable, TCL_STRING_KEYS); + + entry = Tcl_FirstHashEntry(&cdefnPtr->resolveVars, &place); + while (entry) { + vlookup = (ItclVarLookup*)Tcl_GetHashValue(entry); + if (--vlookup->usage == 0) { + /* + * If this is a common variable owned by this class, + * then release the class's hold on it. If it's no + * longer being used, move it into a variable table + * for destruction. + */ + if ( (vlookup->vdefn->member->flags & ITCL_COMMON) != 0 && + vlookup->vdefn->member->classDefn == cdefnPtr ) { + varPtr = (Var*)vlookup->var.common; + if (--varPtr->refCount == 0) { + hPtr = Tcl_CreateHashEntry(&varTable, + vlookup->vdefn->member->fullname, &newEntry); + Tcl_SetHashValue(hPtr, (ClientData) varPtr); + } + } + ckfree((char*)vlookup); + } + entry = Tcl_NextHashEntry(&place); + } + + TclDeleteVars((Interp*)cdefnPtr->interp, &varTable); + Tcl_DeleteHashTable(&cdefnPtr->resolveVars); + + /* + * Tear down the virtual method table... + */ + Tcl_DeleteHashTable(&cdefnPtr->resolveCmds); + + /* + * Delete all variable definitions. + */ + entry = Tcl_FirstHashEntry(&cdefnPtr->variables, &place); + while (entry) { + vdefn = (ItclVarDefn*)Tcl_GetHashValue(entry); + Itcl_DeleteVarDefn(vdefn); + entry = Tcl_NextHashEntry(&place); + } + Tcl_DeleteHashTable(&cdefnPtr->variables); + + /* + * Delete all function definitions. + */ + entry = Tcl_FirstHashEntry(&cdefnPtr->functions, &place); + while (entry) { + Itcl_ReleaseData( Tcl_GetHashValue(entry) ); + entry = Tcl_NextHashEntry(&place); + } + Tcl_DeleteHashTable(&cdefnPtr->functions); + + /* + * Release the claim on all base classes. + */ + elem = Itcl_FirstListElem(&cdefnPtr->bases); + while (elem) { + Itcl_ReleaseData( Itcl_GetListValue(elem) ); + elem = Itcl_NextListElem(elem); + } + Itcl_DeleteList(&cdefnPtr->bases); + Tcl_DeleteHashTable(&cdefnPtr->heritage); + + /* + * Free up the object initialization code. + */ + if (cdefnPtr->initCode) { + Tcl_DecrRefCount(cdefnPtr->initCode); + } + + Itcl_ReleaseData((ClientData)cdefnPtr->info); + + ckfree(cdefnPtr->name); + ckfree(cdefnPtr->fullname); + + ckfree((char*)cdefnPtr); +} + + +/* + * ------------------------------------------------------------------------ + * Itcl_IsClassNamespace() + * + * Checks to see whether or not the given namespace represents an + * [incr Tcl] class. Returns non-zero if so, and zero otherwise. + * ------------------------------------------------------------------------ + */ +int +Itcl_IsClassNamespace(namesp) + Tcl_Namespace *namesp; /* namespace being tested */ +{ + Namespace *nsPtr = (Namespace*)namesp; + + if (nsPtr != NULL) { + return (nsPtr->deleteProc == ItclDestroyClassNamesp); + } + return 0; +} + + +/* + * ------------------------------------------------------------------------ + * Itcl_IsClass() + * + * Checks the given Tcl command to see if it represents an itcl class. + * Returns non-zero if the command is associated with a class. + * ------------------------------------------------------------------------ + */ +int +Itcl_IsClass(cmd) + Tcl_Command cmd; /* command being tested */ +{ + Command *cmdPtr = (Command*)cmd; + + if (cmdPtr->deleteProc == ItclDestroyClass) { + return 1; + } + + /* + * This may be an imported command. Try to get the real + * command and see if it represents a class. + */ + cmdPtr = (Command*)TclGetOriginalCommand(cmd); + if (cmdPtr && cmdPtr->deleteProc == ItclDestroyClass) { + return 1; + } + return 0; +} + + +/* + * ------------------------------------------------------------------------ + * Itcl_FindClass() + * + * Searches for the specified class in the active namespace. If the + * class is found, this procedure returns a pointer to the class + * definition. Otherwise, if the autoload flag is non-zero, an + * attempt will be made to autoload the class definition. If it + * still can't be found, this procedure returns NULL, along with an + * error message in the interpreter. + * ------------------------------------------------------------------------ + */ +ItclClass* +Itcl_FindClass(interp, path, autoload) + Tcl_Interp* interp; /* interpreter containing class */ + char* path; /* path name for class */ +{ + Tcl_Namespace* classNs; + + /* + * Search for a namespace with the specified name, and if + * one is found, see if it is a class namespace. + */ + classNs = Itcl_FindClassNamespace(interp, path); + + if (classNs && Itcl_IsClassNamespace(classNs)) { + return (ItclClass*)classNs->clientData; + } + + /* + * If the autoload flag is set, try to autoload the class + * definition. + */ + if (autoload) { + if (Tcl_VarEval(interp, "::auto_load ", path, (char*)NULL) != TCL_OK) { + char msg[256]; + sprintf(msg, "\n (while attempting to autoload class \"%.200s\")", path); + Tcl_AddErrorInfo(interp, msg); + return NULL; + } + Tcl_ResetResult(interp); + + classNs = Itcl_FindClassNamespace(interp, path); + if (classNs && Itcl_IsClassNamespace(classNs)) { + return (ItclClass*)classNs->clientData; + } + } + + Tcl_AppendResult(interp, "class \"", path, "\" not found in context \"", + Tcl_GetCurrentNamespace(interp)->fullName, "\"", + (char*)NULL); + + return NULL; +} + +/* + * ------------------------------------------------------------------------ + * Itcl_FindClassNamespace() + * + * Searches for the specified class namespace. The normal Tcl procedure + * Tcl_FindNamespace also searches for namespaces, but only in the + * current namespace context. This makes it hard to find one class + * from within another. For example, suppose. you have two namespaces + * Foo and Bar. If you're in the context of Foo and you look for + * Bar, you won't find it with Tcl_FindNamespace. This behavior is + * okay for namespaces, but wrong for classes. + * + * This procedure search for a class namespace. If the name is + * absolute (i.e., starts with "::"), then that one name is checked, + * and the class is either found or not. But if the name is relative, + * it is sought in the current namespace context and in the global + * context, just like the normal command lookup. + * + * This procedure returns a pointer to the desired namespace, or + * NULL if the namespace was not found. + * ------------------------------------------------------------------------ + */ +Tcl_Namespace* +Itcl_FindClassNamespace(interp, path) + Tcl_Interp* interp; /* interpreter containing class */ + char* path; /* path name for class */ +{ + Tcl_Namespace* contextNs = Tcl_GetCurrentNamespace(interp); + Tcl_Namespace* classNs; + Tcl_DString buffer; + + /* + * Look up the namespace. If the name is not absolute, then + * see if it's the current namespace, and try the global + * namespace as well. + */ + classNs = Tcl_FindNamespace(interp, path, (Tcl_Namespace*)NULL, + /* flags */ 0); + + if ( !classNs && contextNs->parentPtr != NULL && + (*path != ':' || *(path+1) != ':') ) { + + if (strcmp(contextNs->name, path) == 0) { + classNs = contextNs; + } + else { + Tcl_DStringInit(&buffer); + Tcl_DStringAppend(&buffer, "::", -1); + Tcl_DStringAppend(&buffer, path, -1); + + classNs = Tcl_FindNamespace(interp, Tcl_DStringValue(&buffer), + (Tcl_Namespace*)NULL, /* flags */ 0); + + Tcl_DStringFree(&buffer); + } + } + return classNs; +} + + +/* + * ------------------------------------------------------------------------ + * Itcl_HandleClass() + * + * Invoked by Tcl whenever the user issues the command associated with + * a class name. Handles the following syntax: + * + * + * ?...? + * + * Without any arguments, the command does nothing. In the olden days, + * this allowed the class name to be invoked by itself to prompt the + * autoloader to load the class definition. Today, this behavior is + * retained for backward compatibility with old releases. + * + * If arguments are specified, then this procedure creates a new + * object named in the appropriate class. Note that if + * contains "#auto", that part is automatically replaced + * by a unique string built from the class name. + * ------------------------------------------------------------------------ + */ +int +Itcl_HandleClass(clientData, interp, objc, objv) + ClientData clientData; /* class definition */ + Tcl_Interp *interp; /* current interpreter */ + int objc; /* number of arguments */ + Tcl_Obj *CONST objv[]; /* argument objects */ +{ + ItclClass *cdefnPtr = (ItclClass*)clientData; + int result = TCL_OK; + + char unique[256]; /* buffer used for unique part of object names */ + Tcl_DString buffer; /* buffer used to build object names */ + char *token, *objName, tmp, *start, *pos, *match; + + ItclObject *newObj; + Tcl_CallFrame frame; + + /* + * If the command is invoked without an object name, then do nothing. + * This used to support autoloading--that the class name could be + * invoked as a command by itself, prompting the autoloader to + * load the class definition. We retain the behavior here for + * backward-compatibility with earlier releases. + */ + if (objc == 1) { + return TCL_OK; + } + + /* + * If the object name is "::", and if this is an old-style class + * definition, then treat the remaining arguments as a command + * in the class namespace. This used to be the way of invoking + * a class proc, but the new syntax is "class::proc" (without + * spaces). + */ + token = Tcl_GetStringFromObj(objv[1], (int*)NULL); + if ((*token == ':') && (strcmp(token,"::") == 0) && (objc > 2)) { + if ((cdefnPtr->flags & ITCL_OLD_STYLE) != 0) { + + result = Tcl_PushCallFrame(interp, &frame, + cdefnPtr->namesp, /* isProcCallFrame */ 0); + + if (result != TCL_OK) { + return result; + } + result = Itcl_EvalArgs(interp, objc-2, objv+2); + + Tcl_PopCallFrame(interp); + return result; + } + + /* + * If this is not an old-style class, then return an error + * describing the syntax change. + */ + Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), + "syntax \"class :: proc\" is an anachronism\n", + "[incr Tcl] no longer supports this syntax.\n", + "Instead, remove the spaces from your procedure invocations:\n", + " ", + Tcl_GetStringFromObj(objv[0], (int*)NULL), "::", + Tcl_GetStringFromObj(objv[2], (int*)NULL), " ?args?", + (char*)NULL); + return TCL_ERROR; + } + + /* + * Otherwise, we have a proper object name. Create a new instance + * with that name. If the name contains "#auto", replace this with + * a uniquely generated string based on the class name. + */ + Tcl_DStringInit(&buffer); + objName = NULL; + + match = "#auto"; + start = token; + for (pos=start; *pos != '\0'; pos++) { + if (*pos == *match) { + if (*(++match) == '\0') { + tmp = *start; + *start = '\0'; /* null-terminate first part */ + + /* + * Substitute a unique part in for "#auto", and keep + * incrementing a counter until a valid name is found. + */ + do { + sprintf(unique,"%.200s%d", cdefnPtr->name, + cdefnPtr->unique++); + unique[0] = tolower(unique[0]); + + Tcl_DStringTrunc(&buffer, 0); + Tcl_DStringAppend(&buffer, token, -1); + Tcl_DStringAppend(&buffer, unique, -1); + Tcl_DStringAppend(&buffer, start+5, -1); + + objName = Tcl_DStringValue(&buffer); + if (Itcl_FindObject(interp, objName, &newObj) != TCL_OK) { + break; /* if an error is found, bail out! */ + } + } while (newObj != NULL); + + *start = tmp; /* undo null-termination */ + objName = Tcl_DStringValue(&buffer); + break; /* object name is ready to go! */ + } + } + else { + match = "#auto"; + pos = start++; + } + } + + /* + * If "#auto" was not found, then just use object name as-is. + */ + if (objName == NULL) { + objName = token; + } + + /* + * Try to create a new object. If successful, return the + * object name as the result of this command. + */ + result = Itcl_CreateObject(interp, objName, cdefnPtr, + objc-2, objv+2, &newObj); + + if (result == TCL_OK) { + Tcl_SetResult(interp, objName, TCL_VOLATILE); + } + + Tcl_DStringFree(&buffer); + return result; +} + + +/* + * ------------------------------------------------------------------------ + * Itcl_ClassCmdResolver() + * + * Used by the class namespaces to handle name resolution for all + * commands. This procedure looks for references to class methods + * and procs, and returns TCL_OK along with the appropriate Tcl + * command in the rPtr argument. If a particular command is private, + * this procedure returns TCL_ERROR and access to the command is + * denied. If a command is not recognized, this procedure returns + * TCL_CONTINUE, and lookup continues via the normal Tcl name + * resolution rules. + * ------------------------------------------------------------------------ + */ +int +Itcl_ClassCmdResolver(interp, name, context, flags, rPtr) + Tcl_Interp *interp; /* current interpreter */ + char* name; /* name of the command being accessed */ + Tcl_Namespace *context; /* namespace performing the resolution */ + int flags; /* TCL_LEAVE_ERR_MSG => leave error messages + * in interp if anything goes wrong */ + Tcl_Command *rPtr; /* returns: resolved command */ +{ + ItclClass *cdefn = (ItclClass*)context->clientData; + + Tcl_HashEntry *entry; + ItclMemberFunc *mfunc; + Command *cmdPtr; + + /* + * If the command is a member function, and if it is + * accessible, return its Tcl command handle. + */ + entry = Tcl_FindHashEntry(&cdefn->resolveCmds, name); + if (!entry) { + return TCL_CONTINUE; + } + + mfunc = (ItclMemberFunc*)Tcl_GetHashValue(entry); + + + /* + * For protected/private functions, figure out whether or + * not the function is accessible from the current context. + * + * TRICKY NOTE: Use Itcl_GetTrueNamespace to determine + * the current context. If the current call frame is + * "transparent", this handles it properly. + */ + if (mfunc->member->protection != ITCL_PUBLIC) { + context = Itcl_GetTrueNamespace(interp, cdefn->info); + + if (!Itcl_CanAccessFunc(mfunc, context)) { + + if ((flags & TCL_LEAVE_ERR_MSG) != 0) { + Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), + "can't access \"", name, "\": ", + Itcl_ProtectionStr(mfunc->member->protection), + " variable", + (char*)NULL); + } + return TCL_ERROR; + } + } + + /* + * Looks like we found an accessible member function. + * + * TRICKY NOTE: Check to make sure that the command handle + * is still valid. If someone has deleted or renamed the + * command, it may not be. This is just the time to catch + * it--as it is being resolved again by the compiler. + */ + cmdPtr = (Command*)mfunc->accessCmd; + if (!cmdPtr || cmdPtr->deleted) { + mfunc->accessCmd = NULL; + + if ((flags & TCL_LEAVE_ERR_MSG) != 0) { + Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), + "can't access \"", name, "\": deleted or redefined\n", + "(use the \"body\" command to redefine methods/procs)", + (char*)NULL); + } + return TCL_ERROR; /* disallow access! */ + } + + *rPtr = mfunc->accessCmd; + return TCL_OK; +} + + +/* + * ------------------------------------------------------------------------ + * Itcl_ClassVarResolver() + * + * Used by the class namespaces to handle name resolution for runtime + * variable accesses. This procedure looks for references to both + * common variables and instance variables at runtime. It is used as + * a second line of defense, to handle references that could not be + * resolved as compiled locals. + * + * If a variable is found, this procedure returns TCL_OK along with + * the appropriate Tcl variable in the rPtr argument. If a particular + * variable is private, this procedure returns TCL_ERROR and access + * to the variable is denied. If a variable is not recognized, this + * procedure returns TCL_CONTINUE, and lookup continues via the normal + * Tcl name resolution rules. + * ------------------------------------------------------------------------ + */ +int +Itcl_ClassVarResolver(interp, name, context, flags, rPtr) + Tcl_Interp *interp; /* current interpreter */ + char* name; /* name of the variable being accessed */ + Tcl_Namespace *context; /* namespace performing the resolution */ + int flags; /* TCL_LEAVE_ERR_MSG => leave error messages + * in interp if anything goes wrong */ + Tcl_Var *rPtr; /* returns: resolved variable */ +{ + ItclClass *cdefn = (ItclClass*)context->clientData; + ItclObject *contextObj; + Tcl_CallFrame *framePtr; + Tcl_HashEntry *entry; + ItclVarLookup *vlookup; + + assert(Itcl_IsClassNamespace(context)); + + /* + * If this is a global variable, handle it in the usual + * Tcl manner. + */ + if (flags & TCL_GLOBAL_ONLY) { + return TCL_CONTINUE; + } + + /* + * See if the variable is a known data member and accessible. + */ + entry = Tcl_FindHashEntry(&cdefn->resolveVars, name); + if (entry == NULL) { + return TCL_CONTINUE; + } + + vlookup = (ItclVarLookup*)Tcl_GetHashValue(entry); + if (!vlookup->accessible) { + return TCL_CONTINUE; + } + + /* + * If this is a common data member, then its variable + * is easy to find. Return it directly. + */ + if ((vlookup->vdefn->member->flags & ITCL_COMMON) != 0) { + *rPtr = vlookup->var.common; + return TCL_OK; + } + + /* + * If this is an instance variable, then we have to + * find the object context, then index into its data + * array to get the actual variable. + */ + framePtr = _Tcl_GetCallFrame(interp, 0); + + entry = Tcl_FindHashEntry(&cdefn->info->contextFrames, (char*)framePtr); + if (entry == NULL) { + return TCL_CONTINUE; + } + contextObj = (ItclObject*)Tcl_GetHashValue(entry); + + /* + * TRICKY NOTE: We've resolved the variable in the current + * class context, but we must also be careful to get its + * index from the most-specific class context. Variables + * are arranged differently depending on which class + * constructed the object. + */ + if (contextObj->classDefn != vlookup->vdefn->member->classDefn) { + entry = Tcl_FindHashEntry(&contextObj->classDefn->resolveVars, + vlookup->vdefn->member->fullname); + + if (entry) { + vlookup = (ItclVarLookup*)Tcl_GetHashValue(entry); + } + } + *rPtr = (Tcl_Var)contextObj->data[vlookup->var.index]; + return TCL_OK; +} + + +/* + * ------------------------------------------------------------------------ + * Itcl_ClassCompiledVarResolver() + * + * Used by the class namespaces to handle name resolution for compile + * time variable accesses. This procedure looks for references to + * both common variables and instance variables at compile time. If + * the variables are found, they are characterized in a generic way + * by their ItclVarLookup record. At runtime, Tcl constructs the + * compiled local variables by calling ItclClassRuntimeVarResolver. + * + * If a variable is found, this procedure returns TCL_OK along with + * information about the variable in the rPtr argument. If a particular + * variable is private, this procedure returns TCL_ERROR and access + * to the variable is denied. If a variable is not recognized, this + * procedure returns TCL_CONTINUE, and lookup continues via the normal + * Tcl name resolution rules. + * ------------------------------------------------------------------------ + */ +int +Itcl_ClassCompiledVarResolver(interp, name, length, context, rPtr) + Tcl_Interp *interp; /* current interpreter */ + char* name; /* name of the variable being accessed */ + int length; /* number of characters in name */ + Tcl_Namespace *context; /* namespace performing the resolution */ + Tcl_ResolvedVarInfo **rPtr; /* returns: info that makes it possible to + * resolve the variable at runtime */ +{ + ItclClass *cdefn = (ItclClass*)context->clientData; + Tcl_HashEntry *entry; + ItclVarLookup *vlookup; + char *buffer, storage[64]; + + assert(Itcl_IsClassNamespace(context)); + + /* + * Copy the name to local storage so we can NULL terminate it. + * If the name is long, allocate extra space for it. + */ + if (length < sizeof(storage)) { + buffer = storage; + } else { + buffer = (char*)ckalloc((unsigned)(length+1)); + } + memcpy((void*)buffer, (void*)name, (size_t)length); + buffer[length] = '\0'; + + entry = Tcl_FindHashEntry(&cdefn->resolveVars, buffer); + + if (buffer != storage) { + ckfree(buffer); + } + + /* + * If the name is not found, or if it is inaccessible, + * continue on with the normal Tcl name resolution rules. + */ + if (entry == NULL) { + return TCL_CONTINUE; + } + + vlookup = (ItclVarLookup*)Tcl_GetHashValue(entry); + if (!vlookup->accessible) { + return TCL_CONTINUE; + } + + /* + * Return the ItclVarLookup record. At runtime, Tcl will + * call ItclClassRuntimeVarResolver with this record, to + * plug in the appropriate variable for the current object + * context. + */ + (*rPtr) = (Tcl_ResolvedVarInfo *) ckalloc(sizeof(ItclResolvedVarInfo)); + (*rPtr)->fetchProc = ItclClassRuntimeVarResolver; + (*rPtr)->deleteProc = NULL; + ((ItclResolvedVarInfo*)(*rPtr))->vlookup = vlookup; + + return TCL_OK; +} + + +/* + * ------------------------------------------------------------------------ + * ItclClassRuntimeVarResolver() + * + * Invoked when Tcl sets up the call frame for an [incr Tcl] method/proc + * at runtime. Resolves data members identified earlier by + * Itcl_ClassCompiledVarResolver. Returns the Tcl_Var representation + * for the data member. + * ------------------------------------------------------------------------ + */ +static Tcl_Var +ItclClassRuntimeVarResolver(interp, resVarInfo) + Tcl_Interp *interp; /* current interpreter */ + Tcl_ResolvedVarInfo *resVarInfo; /* contains ItclVarLookup rep + * for variable */ +{ + ItclVarLookup *vlookup = ((ItclResolvedVarInfo*)resVarInfo)->vlookup; + + Tcl_CallFrame *framePtr; + ItclClass *cdefn; + ItclObject *contextObj; + Tcl_HashEntry *entry; + + /* + * If this is a common data member, then the associated + * variable is known directly. + */ + if ((vlookup->vdefn->member->flags & ITCL_COMMON) != 0) { + return vlookup->var.common; + } + cdefn = vlookup->vdefn->member->classDefn; + + /* + * Otherwise, get the current object context and find the + * variable in its data table. + * + * TRICKY NOTE: Get the index for this variable using the + * virtual table for the MOST-SPECIFIC class. + */ + framePtr = _Tcl_GetCallFrame(interp, 0); + + entry = Tcl_FindHashEntry(&cdefn->info->contextFrames, (char*)framePtr); + if (entry) { + contextObj = (ItclObject*)Tcl_GetHashValue(entry); + + if (contextObj != NULL) { + if (contextObj->classDefn != vlookup->vdefn->member->classDefn) { + entry = Tcl_FindHashEntry(&contextObj->classDefn->resolveVars, + vlookup->vdefn->member->fullname); + + if (entry) { + vlookup = (ItclVarLookup*)Tcl_GetHashValue(entry); + } + } + return (Tcl_Var)contextObj->data[vlookup->var.index]; + } + } + return NULL; +} + + +/* + * ------------------------------------------------------------------------ + * Itcl_BuildVirtualTables() + * + * Invoked whenever the class heritage changes or members are added or + * removed from a class definition to rebuild the member lookup + * tables. There are two tables: + * + * METHODS: resolveCmds + * Used primarily in Itcl_ClassCmdResolver() to resolve all + * command references in a namespace. + * + * DATA MEMBERS: resolveVars + * Used primarily in Itcl_ClassVarResolver() to quickly resolve + * variable references in each class scope. + * + * These tables store every possible name for each command/variable + * (member, class::member, namesp::class::member, etc.). Members + * in a derived class may shadow members with the same name in a + * base class. In that case, the simple name in the resolution + * table will point to the most-specific member. + * ------------------------------------------------------------------------ + */ +void +Itcl_BuildVirtualTables(cdefnPtr) + ItclClass* cdefnPtr; /* class definition being updated */ +{ + Tcl_HashEntry *entry, *hPtr; + Tcl_HashSearch place; + ItclVarLookup *vlookup; + ItclVarDefn *vdefn; + ItclMemberFunc *mfunc; + ItclHierIter hier; + ItclClass *cdPtr; + Namespace* nsPtr; + Tcl_DString buffer, buffer2; + int newEntry; + + Tcl_DStringInit(&buffer); + Tcl_DStringInit(&buffer2); + + /* + * Clear the variable resolution table. + */ + entry = Tcl_FirstHashEntry(&cdefnPtr->resolveVars, &place); + while (entry) { + vlookup = (ItclVarLookup*)Tcl_GetHashValue(entry); + if (--vlookup->usage == 0) { + ckfree((char*)vlookup); + } + entry = Tcl_NextHashEntry(&place); + } + Tcl_DeleteHashTable(&cdefnPtr->resolveVars); + Tcl_InitHashTable(&cdefnPtr->resolveVars, TCL_STRING_KEYS); + cdefnPtr->numInstanceVars = 0; + + /* + * Set aside the first object-specific slot for the built-in + * "this" variable. Only allocate one of these, even though + * there is a definition for "this" in each class scope. + */ + cdefnPtr->numInstanceVars++; + + /* + * Scan through all classes in the hierarchy, from most to + * least specific. Add a lookup entry for each variable + * into the table. + */ + Itcl_InitHierIter(&hier, cdefnPtr); + cdPtr = Itcl_AdvanceHierIter(&hier); + while (cdPtr != NULL) { + entry = Tcl_FirstHashEntry(&cdPtr->variables, &place); + while (entry) { + vdefn = (ItclVarDefn*)Tcl_GetHashValue(entry); + + vlookup = (ItclVarLookup*)ckalloc(sizeof(ItclVarLookup)); + vlookup->vdefn = vdefn; + vlookup->usage = 0; + vlookup->leastQualName = NULL; + + /* + * If this variable is PRIVATE to another class scope, + * then mark it as "inaccessible". + */ + vlookup->accessible = + ( vdefn->member->protection != ITCL_PRIVATE || + vdefn->member->classDefn == cdefnPtr ); + + /* + * If this is a common variable, then keep a reference to + * the variable directly. Otherwise, keep an index into + * the object's variable table. + */ + if ((vdefn->member->flags & ITCL_COMMON) != 0) { + nsPtr = (Namespace*)cdPtr->namesp; + hPtr = Tcl_FindHashEntry(&nsPtr->varTable, vdefn->member->name); + assert(hPtr != NULL); + + vlookup->var.common = (Tcl_Var)Tcl_GetHashValue(hPtr); + } + else { + /* + * If this is a reference to the built-in "this" + * variable, then its index is "0". Otherwise, + * add another slot to the end of the table. + */ + if ((vdefn->member->flags & ITCL_THIS_VAR) != 0) { + vlookup->var.index = 0; + } + else { + vlookup->var.index = cdefnPtr->numInstanceVars++; + } + } + + /* + * Create all possible names for this variable and enter + * them into the variable resolution table: + * var + * class::var + * namesp1::class::var + * namesp2::namesp1::class::var + * ... + */ + Tcl_DStringSetLength(&buffer, 0); + Tcl_DStringAppend(&buffer, vdefn->member->name, -1); + nsPtr = (Namespace*)cdPtr->namesp; + + while (1) { + entry = Tcl_CreateHashEntry(&cdefnPtr->resolveVars, + Tcl_DStringValue(&buffer), &newEntry); + + if (newEntry) { + Tcl_SetHashValue(entry, (ClientData)vlookup); + vlookup->usage++; + + if (!vlookup->leastQualName) { + vlookup->leastQualName = + Tcl_GetHashKey(&cdefnPtr->resolveVars, entry); + } + } + + if (nsPtr == NULL) { + break; + } + Tcl_DStringSetLength(&buffer2, 0); + Tcl_DStringAppend(&buffer2, Tcl_DStringValue(&buffer), -1); + Tcl_DStringSetLength(&buffer, 0); + Tcl_DStringAppend(&buffer, nsPtr->name, -1); + Tcl_DStringAppend(&buffer, "::", -1); + Tcl_DStringAppend(&buffer, Tcl_DStringValue(&buffer2), -1); + + nsPtr = nsPtr->parentPtr; + } + + /* + * If this record is not needed, free it now. + */ + if (vlookup->usage == 0) { + ckfree((char*)vlookup); + } + entry = Tcl_NextHashEntry(&place); + } + cdPtr = Itcl_AdvanceHierIter(&hier); + } + Itcl_DeleteHierIter(&hier); + + /* + * Clear the command resolution table. + */ + Tcl_DeleteHashTable(&cdefnPtr->resolveCmds); + Tcl_InitHashTable(&cdefnPtr->resolveCmds, TCL_STRING_KEYS); + + /* + * Scan through all classes in the hierarchy, from most to + * least specific. Look for the first (most-specific) definition + * of each member function, and enter it into the table. + */ + Itcl_InitHierIter(&hier, cdefnPtr); + cdPtr = Itcl_AdvanceHierIter(&hier); + while (cdPtr != NULL) { + entry = Tcl_FirstHashEntry(&cdPtr->functions, &place); + while (entry) { + mfunc = (ItclMemberFunc*)Tcl_GetHashValue(entry); + + /* + * Create all possible names for this function and enter + * them into the command resolution table: + * func + * class::func + * namesp1::class::func + * namesp2::namesp1::class::func + * ... + */ + Tcl_DStringSetLength(&buffer, 0); + Tcl_DStringAppend(&buffer, mfunc->member->name, -1); + nsPtr = (Namespace*)cdPtr->namesp; + + while (1) { + entry = Tcl_CreateHashEntry(&cdefnPtr->resolveCmds, + Tcl_DStringValue(&buffer), &newEntry); + + if (newEntry) { + Tcl_SetHashValue(entry, (ClientData)mfunc); + } + + if (nsPtr == NULL) { + break; + } + Tcl_DStringSetLength(&buffer2, 0); + Tcl_DStringAppend(&buffer2, Tcl_DStringValue(&buffer), -1); + Tcl_DStringSetLength(&buffer, 0); + Tcl_DStringAppend(&buffer, nsPtr->name, -1); + Tcl_DStringAppend(&buffer, "::", -1); + Tcl_DStringAppend(&buffer, Tcl_DStringValue(&buffer2), -1); + + nsPtr = nsPtr->parentPtr; + } + entry = Tcl_NextHashEntry(&place); + } + cdPtr = Itcl_AdvanceHierIter(&hier); + } + Itcl_DeleteHierIter(&hier); + + Tcl_DStringFree(&buffer); + Tcl_DStringFree(&buffer2); +} + + +/* + * ------------------------------------------------------------------------ + * Itcl_CreateVarDefn() + * + * Creates a new class variable definition. If this is a public + * variable, it may have a bit of "config" code that is used to + * update the object whenever the variable is modified via the + * built-in "configure" method. + * + * Returns TCL_ERROR along with an error message in the specified + * interpreter if anything goes wrong. Otherwise, this returns + * TCL_OK and a pointer to the new variable definition in "vdefnPtr". + * ------------------------------------------------------------------------ + */ +int +Itcl_CreateVarDefn(interp, cdefn, name, init, config, vdefnPtr) + Tcl_Interp *interp; /* interpreter managing this transaction */ + ItclClass* cdefn; /* class containing this variable */ + char* name; /* variable name */ + char* init; /* initial value */ + char* config; /* code invoked when variable is configured */ + ItclVarDefn** vdefnPtr; /* returns: new variable definition */ +{ + int newEntry; + ItclVarDefn *vdefn; + ItclMemberCode *mcode; + Tcl_HashEntry *entry; + + /* + * Add this variable to the variable table for the class. + * Make sure that the variable name does not already exist. + */ + entry = Tcl_CreateHashEntry(&cdefn->variables, name, &newEntry); + if (!newEntry) { + Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), + "variable name \"", name, "\" already defined in class \"", + cdefn->fullname, "\"", + (char*)NULL); + return TCL_ERROR; + } + + /* + * If this variable has some "config" code, try to capture + * its implementation. + */ + if (config) { + if (Itcl_CreateMemberCode(interp, cdefn, (char*)NULL, config, + &mcode) != TCL_OK) { + + Tcl_DeleteHashEntry(entry); + return TCL_ERROR; + } + Itcl_PreserveData((ClientData)mcode); + Itcl_EventuallyFree((ClientData)mcode, Itcl_DeleteMemberCode); + } + else { + mcode = NULL; + } + + + /* + * If everything looks good, create the variable definition. + */ + vdefn = (ItclVarDefn*)ckalloc(sizeof(ItclVarDefn)); + vdefn->member = Itcl_CreateMember(interp, cdefn, name); + vdefn->member->code = mcode; + + if (vdefn->member->protection == ITCL_DEFAULT_PROTECT) { + vdefn->member->protection = ITCL_PROTECTED; + } + + if (init) { + vdefn->init = (char*)ckalloc((unsigned)(strlen(init)+1)); + strcpy(vdefn->init, init); + } + else { + vdefn->init = NULL; + } + + Tcl_SetHashValue(entry, (ClientData)vdefn); + + *vdefnPtr = vdefn; + return TCL_OK; +} + +/* + * ------------------------------------------------------------------------ + * Itcl_DeleteVarDefn() + * + * Destroys a variable definition created by Itcl_CreateVarDefn(), + * freeing all resources associated with it. + * ------------------------------------------------------------------------ + */ +void +Itcl_DeleteVarDefn(vdefn) + ItclVarDefn *vdefn; /* variable definition to be destroyed */ +{ + Itcl_DeleteMember(vdefn->member); + + if (vdefn->init) { + ckfree(vdefn->init); + } + ckfree((char*)vdefn); +} + + +/* + * ------------------------------------------------------------------------ + * Itcl_GetCommonVar() + * + * Returns the current value for a common class variable. The member + * name is interpreted with respect to the given class scope. That + * scope is installed as the current context before querying the + * variable. This by-passes the protection level in case the variable + * is "private". + * + * If successful, this procedure returns a pointer to a string value + * which remains alive until the variable changes it value. If + * anything goes wrong, this returns NULL. + * ------------------------------------------------------------------------ + */ +char* +Itcl_GetCommonVar(interp, name, contextClass) + Tcl_Interp *interp; /* current interpreter */ + char *name; /* name of desired instance variable */ + ItclClass *contextClass; /* name is interpreted in this scope */ +{ + char *val = NULL; + int result; + Tcl_CallFrame frame; + + /* + * Activate the namespace for the given class. That installs + * the appropriate name resolution rules and by-passes any + * security restrictions. + */ + result = Tcl_PushCallFrame(interp, &frame, + contextClass->namesp, /*isProcCallFrame*/ 0); + + if (result == TCL_OK) { + val = Tcl_GetVar2(interp, name, (char*)NULL, 0); + Tcl_PopCallFrame(interp); + } + return val; +} + + +/* + * ------------------------------------------------------------------------ + * Itcl_CreateMember() + * + * Creates the data record representing a class member. This is the + * generic representation for a data member or member function. + * Returns a pointer to the new representation. + * ------------------------------------------------------------------------ + */ +ItclMember* +Itcl_CreateMember(interp, cdefn, name) + Tcl_Interp* interp; /* interpreter managing this action */ + ItclClass *cdefn; /* class definition */ + char* name; /* name of new member */ +{ + ItclMember *memPtr; + int fullsize; + + /* + * Allocate the memory for a class member and fill in values. + */ + memPtr = (ItclMember*)ckalloc(sizeof(ItclMember)); + memPtr->interp = interp; + memPtr->classDefn = cdefn; + memPtr->flags = 0; + memPtr->protection = Itcl_Protection(interp, 0); + memPtr->code = NULL; + + fullsize = strlen(cdefn->fullname) + strlen(name) + 2; + memPtr->fullname = (char*)ckalloc((unsigned)(fullsize+1)); + strcpy(memPtr->fullname, cdefn->fullname); + strcat(memPtr->fullname, "::"); + strcat(memPtr->fullname, name); + + memPtr->name = (char*)ckalloc((unsigned)(strlen(name)+1)); + strcpy(memPtr->name, name); + + return memPtr; +} + + +/* + * ------------------------------------------------------------------------ + * Itcl_DeleteMember() + * + * Destroys all data associated with the given member function definition. + * Usually invoked by the interpreter when a member function is deleted. + * ------------------------------------------------------------------------ + */ +void +Itcl_DeleteMember(memPtr) + ItclMember *memPtr; /* pointer to member function definition */ +{ + if (memPtr) { + ckfree(memPtr->name); + ckfree(memPtr->fullname); + + if (memPtr->code) { + Itcl_ReleaseData((ClientData)memPtr->code); + } + memPtr->code = NULL; + + ckfree((char*)memPtr); + } +} + + +/* + * ------------------------------------------------------------------------ + * Itcl_InitHierIter() + * + * Initializes an iterator for traversing the hierarchy of the given + * class. Subsequent calls to Itcl_AdvanceHierIter() will return + * the base classes in order from most-to-least specific. + * ------------------------------------------------------------------------ + */ +void +Itcl_InitHierIter(iter,cdefn) + ItclHierIter *iter; /* iterator used for traversal */ + ItclClass *cdefn; /* class definition for start of traversal */ +{ + Itcl_InitStack(&iter->stack); + Itcl_PushStack((ClientData)cdefn, &iter->stack); + iter->current = cdefn; +} + +/* + * ------------------------------------------------------------------------ + * Itcl_DeleteHierIter() + * + * Destroys an iterator for traversing class hierarchies, freeing + * all memory associated with it. + * ------------------------------------------------------------------------ + */ +void +Itcl_DeleteHierIter(iter) + ItclHierIter *iter; /* iterator used for traversal */ +{ + Itcl_DeleteStack(&iter->stack); + iter->current = NULL; +} + +/* + * ------------------------------------------------------------------------ + * Itcl_AdvanceHierIter() + * + * Moves a class hierarchy iterator forward to the next base class. + * Returns a pointer to the current class definition, or NULL when + * the end of the hierarchy has been reached. + * ------------------------------------------------------------------------ + */ +ItclClass* +Itcl_AdvanceHierIter(iter) + ItclHierIter *iter; /* iterator used for traversal */ +{ + register Itcl_ListElem *elem; + ItclClass *cdPtr; + + iter->current = (ItclClass*)Itcl_PopStack(&iter->stack); + + /* + * Push classes onto the stack in reverse order, so that + * they will be popped off in the proper order. + */ + if (iter->current) { + cdPtr = (ItclClass*)iter->current; + elem = Itcl_LastListElem(&cdPtr->bases); + while (elem) { + Itcl_PushStack(Itcl_GetListValue(elem), &iter->stack); + elem = Itcl_PrevListElem(elem); + } + } + return iter->current; +}
itcl_class.c Property changes : Added: svn:executable ## -0,0 +1 ## +* \ No newline at end of property Index: itcl_cmds.c =================================================================== --- itcl_cmds.c (nonexistent) +++ itcl_cmds.c (revision 1765) @@ -0,0 +1,1359 @@ +/* + * ------------------------------------------------------------------------ + * PACKAGE: [incr Tcl] + * DESCRIPTION: Object-Oriented Extensions to Tcl + * + * [incr Tcl] provides object-oriented extensions to Tcl, much as + * C++ provides object-oriented extensions to C. It provides a means + * of encapsulating related procedures together with their shared data + * in a local namespace that is hidden from the outside world. It + * promotes code re-use through inheritance. More than anything else, + * it encourages better organization of Tcl applications through the + * object-oriented paradigm, leading to code that is easier to + * understand and maintain. + * + * This file defines information that tracks classes and objects + * at a global level for a given interpreter. + * + * ======================================================================== + * AUTHOR: Michael J. McLennan + * Bell Labs Innovations for Lucent Technologies + * mmclennan@lucent.com + * http://www.tcltk.com/itcl + * + * RCS: $Id: itcl_cmds.c,v 1.1.1.1 2002-01-16 10:24:46 markom Exp $ + * ======================================================================== + * Copyright (c) 1993-1998 Lucent Technologies, Inc. + * ------------------------------------------------------------------------ + * See the file "license.terms" for information on usage and redistribution + * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + */ +#include "itclInt.h" + +/* + * FORWARD DECLARATIONS + */ +static void ItclDelObjectInfo _ANSI_ARGS_((char* cdata)); +static int Initialize _ANSI_ARGS_((Tcl_Interp *interp)); +static int ItclHandleStubCmd _ANSI_ARGS_((ClientData clientData, + Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])); +static void ItclDeleteStub _ANSI_ARGS_((ClientData cdata)); + +/* + * The following string is the startup script executed in new + * interpreters. It locates the Tcl code in the [incr Tcl] library + * directory and loads it in. + */ + +static char initScript[] = "\n\ +namespace eval ::itcl {\n\ + proc _find_init {} {\n\ + global env tcl_library\n\ + variable library\n\ + variable version\n\ + rename _find_init {}\n\ + if {[catch {uplevel #0 source -rsrc itcl}] == 0} {\n\ + return\n\ + }\n\ + tcl_findLibrary itcl 3.0 {} itcl.tcl ITCL_LIBRARY ::itcl::library {} {} itcl\n\ + }\n\ + _find_init\n\ +}"; + +/* + * The following script is used to initialize Itcl in a safe interpreter. + */ + +static char safeInitScript[] = +"proc ::itcl::local {class name args} {\n\ + set ptr [uplevel eval [list $class $name] $args]\n\ + uplevel [list set itcl-local-$ptr $ptr]\n\ + set cmd [uplevel namespace which -command $ptr]\n\ + uplevel [list trace variable itcl-local-$ptr u \"::itcl::delete object $cmd; list\"]\n\ + return $ptr\n\ +}"; + + +/* + * ------------------------------------------------------------------------ + * Initialize() + * + * Invoked whenever a new interpeter is created to install the + * [incr Tcl] package. Usually invoked within Tcl_AppInit() at + * the start of execution. + * + * Creates the "::itcl" namespace and installs access commands for + * creating classes and querying info. + * + * Returns TCL_OK on success, or TCL_ERROR (along with an error + * message in the interpreter) if anything goes wrong. + * ------------------------------------------------------------------------ + */ +static int +Initialize(interp) + Tcl_Interp *interp; /* interpreter to be updated */ +{ + Tcl_CmdInfo cmdInfo; + Tcl_Namespace *itclNs; + ItclObjectInfo *info; + + if (Tcl_PkgRequire(interp, "Tcl", TCL_VERSION, 1) == NULL) { + return TCL_ERROR; + } + + /* + * See if [incr Tcl] is already installed. + */ + if (Tcl_GetCommandInfo(interp, "::itcl::class", &cmdInfo)) { + Tcl_SetResult(interp, "already installed: [incr Tcl]", TCL_STATIC); + return TCL_ERROR; + } + + /* + * Initialize the ensemble package first, since we need this + * for other parts of [incr Tcl]. + */ + if (Itcl_EnsembleInit(interp) != TCL_OK) { + return TCL_ERROR; + } + + /* + * Create the top-level data structure for tracking objects. + * Store this as "associated data" for easy access, but link + * it to the itcl namespace for ownership. + */ + info = (ItclObjectInfo*)ckalloc(sizeof(ItclObjectInfo)); + info->interp = interp; + Tcl_InitHashTable(&info->objects, TCL_ONE_WORD_KEYS); + Itcl_InitStack(&info->transparentFrames); + Tcl_InitHashTable(&info->contextFrames, TCL_ONE_WORD_KEYS); + info->protection = ITCL_DEFAULT_PROTECT; + Itcl_InitStack(&info->cdefnStack); + + Tcl_SetAssocData(interp, ITCL_INTERP_DATA, + (Tcl_InterpDeleteProc*)NULL, (ClientData)info); + + /* + * Install commands into the "::itcl" namespace. + */ + Tcl_CreateObjCommand(interp, "::itcl::class", Itcl_ClassCmd, + (ClientData)info, Itcl_ReleaseData); + Itcl_PreserveData((ClientData)info); + + Tcl_CreateObjCommand(interp, "::itcl::body", Itcl_BodyCmd, + (ClientData)NULL, (Tcl_CmdDeleteProc*)NULL); + + Tcl_CreateObjCommand(interp, "::itcl::configbody", Itcl_ConfigBodyCmd, + (ClientData)NULL, (Tcl_CmdDeleteProc*)NULL); + + Itcl_EventuallyFree((ClientData)info, ItclDelObjectInfo); + + /* + * Create the "itcl::find" command for high-level queries. + */ + if (Itcl_CreateEnsemble(interp, "::itcl::find") != TCL_OK) { + return TCL_ERROR; + } + if (Itcl_AddEnsemblePart(interp, "::itcl::find", + "classes", "?pattern?", + Itcl_FindClassesCmd, + (ClientData)info, Itcl_ReleaseData) != TCL_OK) { + return TCL_ERROR; + } + Itcl_PreserveData((ClientData)info); + + if (Itcl_AddEnsemblePart(interp, "::itcl::find", + "objects", "?-class className? ?-isa className? ?pattern?", + Itcl_FindObjectsCmd, + (ClientData)info, Itcl_ReleaseData) != TCL_OK) { + return TCL_ERROR; + } + Itcl_PreserveData((ClientData)info); + + + /* + * Create the "itcl::delete" command to delete objects + * and classes. + */ + if (Itcl_CreateEnsemble(interp, "::itcl::delete") != TCL_OK) { + return TCL_ERROR; + } + if (Itcl_AddEnsemblePart(interp, "::itcl::delete", + "class", "name ?name...?", + Itcl_DelClassCmd, + (ClientData)info, Itcl_ReleaseData) != TCL_OK) { + return TCL_ERROR; + } + Itcl_PreserveData((ClientData)info); + + if (Itcl_AddEnsemblePart(interp, "::itcl::delete", + "object", "name ?name...?", + Itcl_DelObjectCmd, + (ClientData)info, Itcl_ReleaseData) != TCL_OK) { + return TCL_ERROR; + } + Itcl_PreserveData((ClientData)info); + + /* + * Add "code" and "scope" commands for handling scoped values. + */ + Tcl_CreateObjCommand(interp, "::itcl::code", Itcl_CodeCmd, + (ClientData)NULL, (Tcl_CmdDeleteProc*)NULL); + + Tcl_CreateObjCommand(interp, "::itcl::scope", Itcl_ScopeCmd, + (ClientData)NULL, (Tcl_CmdDeleteProc*)NULL); + + /* + * Add commands for handling import stubs at the Tcl level. + */ + if (Itcl_CreateEnsemble(interp, "::itcl::import::stub") != TCL_OK) { + return TCL_ERROR; + } + if (Itcl_AddEnsemblePart(interp, "::itcl::import::stub", + "create", "name", Itcl_StubCreateCmd, + (ClientData)NULL, (Tcl_CmdDeleteProc*)NULL) != TCL_OK) { + return TCL_ERROR; + } + if (Itcl_AddEnsemblePart(interp, "::itcl::import::stub", + "exists", "name", Itcl_StubExistsCmd, + (ClientData)NULL, (Tcl_CmdDeleteProc*)NULL) != TCL_OK) { + return TCL_ERROR; + } + + /* + * Install a variable resolution procedure to handle scoped + * values everywhere within the interpreter. + */ + Tcl_AddInterpResolvers(interp, "itcl", (Tcl_ResolveCmdProc*)NULL, + Itcl_ScopedVarResolver, (Tcl_ResolveCompiledVarProc*)NULL); + + /* + * Install the "itcl::parser" namespace used to parse the + * class definitions. + */ + if (Itcl_ParseInit(interp, info) != TCL_OK) { + return TCL_ERROR; + } + + /* + * Create "itcl::builtin" namespace for commands that + * are automatically built into class definitions. + */ + if (Itcl_BiInit(interp) != TCL_OK) { + return TCL_ERROR; + } + + /* + * Install stuff needed for backward compatibility with previous + * version of [incr Tcl]. + */ + if (Itcl_OldInit(interp, info) != TCL_OK) { + return TCL_ERROR; + } + + /* + * Export all commands in the "itcl" namespace so that they + * can be imported with something like "namespace import itcl::*" + */ + itclNs = Tcl_FindNamespace(interp, "::itcl", (Tcl_Namespace*)NULL, + TCL_LEAVE_ERR_MSG); + + if (!itclNs || + Tcl_Export(interp, itclNs, "*", /* resetListFirst */ 1) != TCL_OK) { + return TCL_ERROR; + } + + /* + * Set up the variables containing version info. + */ + + Tcl_SetVar(interp, "::itcl::patchLevel", ITCL_PATCH_LEVEL, + TCL_NAMESPACE_ONLY); + + Tcl_SetVar(interp, "::itcl::version", ITCL_VERSION, + TCL_NAMESPACE_ONLY); + + /* + * Package is now loaded. + */ + if (Tcl_PkgProvide(interp, "Itcl", ITCL_VERSION) != TCL_OK) { + return TCL_ERROR; + } + return TCL_OK; +} + + +/* + * ------------------------------------------------------------------------ + * Itcl_Init() + * + * Invoked whenever a new INTERPRETER is created to install the + * [incr Tcl] package. Usually invoked within Tcl_AppInit() at + * the start of execution. + * + * Creates the "::itcl" namespace and installs access commands for + * creating classes and querying info. + * + * Returns TCL_OK on success, or TCL_ERROR (along with an error + * message in the interpreter) if anything goes wrong. + * ------------------------------------------------------------------------ + */ +int +Itcl_Init(interp) + Tcl_Interp *interp; /* interpreter to be updated */ +{ + if (Initialize(interp) != TCL_OK) { + return TCL_ERROR; + } + return Tcl_Eval(interp, initScript); +} + + +/* + * ------------------------------------------------------------------------ + * Itcl_SafeInit() + * + * Invoked whenever a new SAFE INTERPRETER is created to install + * the [incr Tcl] package. + * + * Creates the "::itcl" namespace and installs access commands for + * creating classes and querying info. + * + * Returns TCL_OK on success, or TCL_ERROR (along with an error + * message in the interpreter) if anything goes wrong. + * ------------------------------------------------------------------------ + */ +int +Itcl_SafeInit(interp) + Tcl_Interp *interp; /* interpreter to be updated */ +{ + if (Initialize(interp) != TCL_OK) { + return TCL_ERROR; + } + return Tcl_Eval(interp, safeInitScript); +} + + +/* + * ------------------------------------------------------------------------ + * ItclDelObjectInfo() + * + * Invoked when the management info for [incr Tcl] is no longer being + * used in an interpreter. This will only occur when all class + * manipulation commands are removed from the interpreter. + * ------------------------------------------------------------------------ + */ +static void +ItclDelObjectInfo(cdata) + char* cdata; /* client data for class command */ +{ + ItclObjectInfo *info = (ItclObjectInfo*)cdata; + + ItclObject *contextObj; + Tcl_HashSearch place; + Tcl_HashEntry *entry; + + /* + * Destroy all known objects by deleting their access + * commands. + */ + entry = Tcl_FirstHashEntry(&info->objects, &place); + while (entry) { + contextObj = (ItclObject*)Tcl_GetHashValue(entry); + Tcl_DeleteCommandFromToken(info->interp, contextObj->accessCmd); + entry = Tcl_NextHashEntry(&place); + } + Tcl_DeleteHashTable(&info->objects); + + /* + * Discard all known object contexts. + */ + entry = Tcl_FirstHashEntry(&info->contextFrames, &place); + while (entry) { + Itcl_ReleaseData( Tcl_GetHashValue(entry) ); + entry = Tcl_NextHashEntry(&place); + } + Tcl_DeleteHashTable(&info->contextFrames); + + Itcl_DeleteStack(&info->transparentFrames); + Itcl_DeleteStack(&info->cdefnStack); + ckfree((char*)info); +} + + +/* + * ------------------------------------------------------------------------ + * Itcl_FindClassesCmd() + * + * Part of the "::info" ensemble. Invoked by Tcl whenever the user + * issues an "info classes" command to query the list of classes + * in the current namespace. Handles the following syntax: + * + * info classes ?? + * + * Returns TCL_OK/TCL_ERROR to indicate success/failure. + * ------------------------------------------------------------------------ + */ +/* ARGSUSED */ +int +Itcl_FindClassesCmd(clientData, interp, objc, objv) + ClientData clientData; /* class/object info */ + Tcl_Interp *interp; /* current interpreter */ + int objc; /* number of arguments */ + Tcl_Obj *CONST objv[]; /* argument objects */ +{ + Tcl_Namespace *activeNs = Tcl_GetCurrentNamespace(interp); + Tcl_Namespace *globalNs = Tcl_GetGlobalNamespace(interp); + int forceFullNames = 0; + + char *pattern; + char *name; + int i, nsearch, newEntry; + Tcl_HashTable unique; + Tcl_HashEntry *entry; + Tcl_HashSearch place; + Tcl_Namespace *search[2]; + Tcl_Command cmd, originalCmd; + Namespace *nsPtr; + Tcl_Obj *listPtr, *objPtr; + + if (objc > 2) { + Tcl_WrongNumArgs(interp, 1, objv, "?pattern?"); + return TCL_ERROR; + } + + if (objc == 2) { + pattern = Tcl_GetStringFromObj(objv[1], (int*)NULL); + forceFullNames = (strstr(pattern, "::") != NULL); + } else { + pattern = NULL; + } + + /* + * Search through all commands in the current namespace and + * in the global namespace. If we find any commands that + * represent classes, report them. + */ + listPtr = Tcl_NewListObj(0, (Tcl_Obj* CONST*)NULL); + + nsearch = 0; + search[nsearch++] = activeNs; + if (activeNs != globalNs) { + search[nsearch++] = globalNs; + } + + Tcl_InitHashTable(&unique, TCL_ONE_WORD_KEYS); + + for (i=0; i < nsearch; i++) { + nsPtr = (Namespace*)search[i]; + + entry = Tcl_FirstHashEntry(&nsPtr->cmdTable, &place); + while (entry) { + cmd = (Tcl_Command)Tcl_GetHashValue(entry); + if (Itcl_IsClass(cmd)) { + originalCmd = TclGetOriginalCommand(cmd); + + /* + * Report full names if: + * - the pattern has namespace qualifiers + * - the class namespace is not in the current namespace + * - the class's object creation command is imported from + * another namespace. + * + * Otherwise, report short names. + */ + if (forceFullNames || nsPtr != (Namespace*)activeNs || + originalCmd != NULL) { + + objPtr = Tcl_NewStringObj((char*)NULL, 0); + Tcl_GetCommandFullName(interp, cmd, objPtr); + name = Tcl_GetStringFromObj(objPtr, (int*)NULL); + } else { + name = Tcl_GetCommandName(interp, cmd); + objPtr = Tcl_NewStringObj(name, -1); + } + + if (originalCmd) { + cmd = originalCmd; + } + Tcl_CreateHashEntry(&unique, (char*)cmd, &newEntry); + + if (newEntry && (!pattern || Tcl_StringMatch(name, pattern))) { + Tcl_ListObjAppendElement((Tcl_Interp*)NULL, + listPtr, objPtr); + } + } + entry = Tcl_NextHashEntry(&place); + } + } + Tcl_DeleteHashTable(&unique); + + Tcl_SetObjResult(interp, listPtr); + return TCL_OK; +} + + +/* + * ------------------------------------------------------------------------ + * Itcl_FindObjectsCmd() + * + * Part of the "::info" ensemble. Invoked by Tcl whenever the user + * issues an "info objects" command to query the list of known objects. + * Handles the following syntax: + * + * info objects ?-class ? ?-isa ? ?? + * + * Returns TCL_OK/TCL_ERROR to indicate success/failure. + * ------------------------------------------------------------------------ + */ +int +Itcl_FindObjectsCmd(clientData, interp, objc, objv) + ClientData clientData; /* class/object info */ + Tcl_Interp *interp; /* current interpreter */ + int objc; /* number of arguments */ + Tcl_Obj *CONST objv[]; /* argument objects */ +{ + Tcl_Namespace *activeNs = Tcl_GetCurrentNamespace(interp); + Tcl_Namespace *globalNs = Tcl_GetGlobalNamespace(interp); + int forceFullNames = 0; + + char *pattern = NULL; + ItclClass *classDefn = NULL; + ItclClass *isaDefn = NULL; + + char *name, *token; + int i, pos, nsearch, newEntry, match; + ItclObject *contextObj; + Tcl_HashTable unique; + Tcl_HashEntry *entry; + Tcl_HashSearch place; + Tcl_Namespace *search[2]; + Tcl_Command cmd, originalCmd; + Namespace *nsPtr; + Command *cmdPtr; + Tcl_Obj *listPtr, *objPtr; + + /* + * Parse arguments: + * objects ?-class ? ?-isa ? ?? + */ + pos = 0; + while (++pos < objc) { + token = Tcl_GetStringFromObj(objv[pos], (int*)NULL); + if (*token != '-') { + if (!pattern) { + pattern = token; + forceFullNames = (strstr(pattern, "::") != NULL); + } else { + break; + } + } + else if ((pos+1 < objc) && (strcmp(token,"-class") == 0)) { + name = Tcl_GetStringFromObj(objv[pos+1], (int*)NULL); + classDefn = Itcl_FindClass(interp, name, /* autoload */ 1); + if (classDefn == NULL) { + return TCL_ERROR; + } + pos++; + } + else if ((pos+1 < objc) && (strcmp(token,"-isa") == 0)) { + name = Tcl_GetStringFromObj(objv[pos+1], (int*)NULL); + isaDefn = Itcl_FindClass(interp, name, /* autoload */ 1); + if (isaDefn == NULL) { + return TCL_ERROR; + } + pos++; + } + else { + break; + } + } + + if (pos < objc) { + Tcl_WrongNumArgs(interp, 1, objv, + "?-class className? ?-isa className? ?pattern?"); + return TCL_ERROR; + } + + /* + * Search through all commands in the current namespace and + * in the global namespace. If we find any commands that + * represent objects, report them. + */ + listPtr = Tcl_NewListObj(0, (Tcl_Obj* CONST*)NULL); + + nsearch = 0; + search[nsearch++] = activeNs; + if (activeNs != globalNs) { + search[nsearch++] = globalNs; + } + + Tcl_InitHashTable(&unique, TCL_ONE_WORD_KEYS); + + for (i=0; i < nsearch; i++) { + nsPtr = (Namespace*)search[i]; + + entry = Tcl_FirstHashEntry(&nsPtr->cmdTable, &place); + while (entry) { + cmd = (Tcl_Command)Tcl_GetHashValue(entry); + if (Itcl_IsObject(cmd)) { + originalCmd = TclGetOriginalCommand(cmd); + if (originalCmd) { + cmd = originalCmd; + } + cmdPtr = (Command*)cmd; + contextObj = (ItclObject*)cmdPtr->objClientData; + + /* + * Report full names if: + * - the pattern has namespace qualifiers + * - the class namespace is not in the current namespace + * - the class's object creation command is imported from + * another namespace. + * + * Otherwise, report short names. + */ + if (forceFullNames || nsPtr != (Namespace*)activeNs || + originalCmd != NULL) { + + objPtr = Tcl_NewStringObj((char*)NULL, 0); + Tcl_GetCommandFullName(interp, cmd, objPtr); + name = Tcl_GetStringFromObj(objPtr, (int*)NULL); + } else { + name = Tcl_GetCommandName(interp, cmd); + objPtr = Tcl_NewStringObj(name, -1); + } + + Tcl_CreateHashEntry(&unique, (char*)cmd, &newEntry); + + match = 0; + if (newEntry && (!pattern || Tcl_StringMatch(name, pattern))) { + if (!classDefn || (contextObj->classDefn == classDefn)) { + if (!isaDefn) { + match = 1; + } else { + entry = Tcl_FindHashEntry( + &contextObj->classDefn->heritage, + (char*)isaDefn); + + if (entry) { + match = 1; + } + } + } + } + + if (match) { + Tcl_ListObjAppendElement((Tcl_Interp*)NULL, + listPtr, objPtr); + } else { + Tcl_IncrRefCount(objPtr); /* throw away the name */ + Tcl_DecrRefCount(objPtr); + } + } + entry = Tcl_NextHashEntry(&place); + } + } + Tcl_DeleteHashTable(&unique); + + Tcl_SetObjResult(interp, listPtr); + return TCL_OK; +} + + +/* + * ------------------------------------------------------------------------ + * Itcl_ProtectionCmd() + * + * Invoked by Tcl whenever the user issues a protection setting + * command like "public" or "private". Creates commands and + * variables, and assigns a protection level to them. Protection + * levels are defined as follows: + * + * public => accessible from any namespace + * protected => accessible from selected namespaces + * private => accessible only in the namespace where it was defined + * + * Handles the following syntax: + * + * public ? ...? + * + * Returns TCL_OK/TCL_ERROR to indicate success/failure. + * ------------------------------------------------------------------------ + */ +int +Itcl_ProtectionCmd(clientData, interp, objc, objv) + ClientData clientData; /* protection level (public/protected/private) */ + Tcl_Interp *interp; /* current interpreter */ + int objc; /* number of arguments */ + Tcl_Obj *CONST objv[]; /* argument objects */ +{ + int pLevel = (int)clientData; + + int result; + int oldLevel; + + if (objc < 2) { + Tcl_WrongNumArgs(interp, 1, objv, "command ?arg arg...?"); + return TCL_ERROR; + } + + oldLevel = Itcl_Protection(interp, pLevel); + + if (objc == 2) { + /* CYGNUS LOCAL - Fix for 8.1 */ +#if TCL_MAJOR_VERSION == 8 && TCL_MINOR_VERSION == 0 + result = Tcl_EvalObj(interp, objv[1]); +#else + result = Tcl_EvalObj(interp, objv[1], 0); +#endif + /* END CYGNUS LOCAL */ + } else { + result = Itcl_EvalArgs(interp, objc-1, objv+1); + } + + if (result == TCL_BREAK) { + Tcl_SetResult(interp, "invoked \"break\" outside of a loop", + TCL_STATIC); + result = TCL_ERROR; + } + else if (result == TCL_CONTINUE) { + Tcl_SetResult(interp, "invoked \"continue\" outside of a loop", + TCL_STATIC); + result = TCL_ERROR; + } + else if (result != TCL_OK) { + char mesg[256], *name; + name = Tcl_GetStringFromObj(objv[0], (int*)NULL); + sprintf(mesg, "\n (%.100s body line %d)", + name, interp->errorLine); + Tcl_AddErrorInfo(interp, mesg); + } + + Itcl_Protection(interp, oldLevel); + return result; +} + + +/* + * ------------------------------------------------------------------------ + * Itcl_DelClassCmd() + * + * Part of the "delete" ensemble. Invoked by Tcl whenever the + * user issues a "delete class" command to delete classes. + * Handles the following syntax: + * + * delete class ?...? + * + * Returns TCL_OK/TCL_ERROR to indicate success/failure. + * ------------------------------------------------------------------------ + */ +/* ARGSUSED */ +int +Itcl_DelClassCmd(clientData, interp, objc, objv) + ClientData clientData; /* unused */ + Tcl_Interp *interp; /* current interpreter */ + int objc; /* number of arguments */ + Tcl_Obj *CONST objv[]; /* argument objects */ +{ + int i; + char *name; + ItclClass *cdefn; + + /* + * Since destroying a base class will destroy all derived + * classes, calls like "destroy class Base Derived" could + * fail. Break this into two passes: first check to make + * sure that all classes on the command line are valid, + * then delete them. + */ + for (i=1; i < objc; i++) { + name = Tcl_GetStringFromObj(objv[i], (int*)NULL); + cdefn = Itcl_FindClass(interp, name, /* autoload */ 1); + if (cdefn == NULL) { + return TCL_ERROR; + } + } + + for (i=1; i < objc; i++) { + name = Tcl_GetStringFromObj(objv[i], (int*)NULL); + cdefn = Itcl_FindClass(interp, name, /* autoload */ 0); + + if (cdefn) { + Tcl_ResetResult(interp); + if (Itcl_DeleteClass(interp, cdefn) != TCL_OK) { + return TCL_ERROR; + } + } + } + Tcl_ResetResult(interp); + return TCL_OK; +} + + +/* + * ------------------------------------------------------------------------ + * Itcl_DelObjectCmd() + * + * Part of the "delete" ensemble. Invoked by Tcl whenever the user + * issues a "delete object" command to delete [incr Tcl] objects. + * Handles the following syntax: + * + * delete object ?...? + * + * Returns TCL_OK/TCL_ERROR to indicate success/failure. + * ------------------------------------------------------------------------ + */ +int +Itcl_DelObjectCmd(clientData, interp, objc, objv) + ClientData clientData; /* object management info */ + Tcl_Interp *interp; /* current interpreter */ + int objc; /* number of arguments */ + Tcl_Obj *CONST objv[]; /* argument objects */ +{ + int i; + char *name; + ItclObject *contextObj; + + /* + * Scan through the list of objects and attempt to delete them. + * If anything goes wrong (i.e., destructors fail), then + * abort with an error. + */ + for (i=1; i < objc; i++) { + name = Tcl_GetStringFromObj(objv[i], (int*)NULL); + if (Itcl_FindObject(interp, name, &contextObj) != TCL_OK) { + return TCL_ERROR; + } + + if (contextObj == NULL) { + Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), + "object \"", name, "\" not found", + (char*)NULL); + return TCL_ERROR; + } + + if (Itcl_DeleteObject(interp, contextObj) != TCL_OK) { + return TCL_ERROR; + } + } + return TCL_OK; +} + + +/* + * ------------------------------------------------------------------------ + * Itcl_ScopeCmd() + * + * Invoked by Tcl whenever the user issues a "scope" command to + * create a fully qualified variable name. Handles the following + * syntax: + * + * scope + * + * If the input string is already fully qualified (starts with "::"), + * then this procedure does nothing. Otherwise, it looks for a + * data member called and returns its fully qualified + * name. If the is a common data member, this procedure + * returns a name of the form: + * + * ::namesp::namesp::class::variable + * + * If the is an instance variable, this procedure returns + * a name of the form: + * + * @itcl ::namesp::namesp::object variable + * + * This kind of scoped value is recognized by the Itcl_ScopedVarResolver + * proc, which handles variable resolution for the entire interpreter. + * + * Returns TCL_OK/TCL_ERROR to indicate success/failure. + * ------------------------------------------------------------------------ + */ +/* ARGSUSED */ +int +Itcl_ScopeCmd(dummy, interp, objc, objv) + ClientData dummy; /* unused */ + Tcl_Interp *interp; /* current interpreter */ + int objc; /* number of arguments */ + Tcl_Obj *CONST objv[]; /* argument objects */ +{ + int result = TCL_OK; + Tcl_Namespace *contextNs = Tcl_GetCurrentNamespace(interp); + char *openParen = NULL; + + register char *p; + char *token; + ItclClass *contextClass; + ItclObject *contextObj; + ItclObjectInfo *info; + Tcl_CallFrame *framePtr; + Tcl_HashEntry *entry; + ItclVarLookup *vlookup; + Tcl_Obj *objPtr; + Tcl_Var var; + + if (objc != 2) { + Tcl_WrongNumArgs(interp, 1, objv, "varname"); + return TCL_ERROR; + } + + /* + * If this looks like a fully qualified name already, + * then return it as is. + */ + token = Tcl_GetStringFromObj(objv[1], (int*)NULL); + if (*token == ':' && *(token+1) == ':') { + Tcl_SetObjResult(interp, objv[1]); + return TCL_OK; + } + + /* + * If the variable name is an array reference, pick out + * the array name and use that for the lookup operations + * below. + */ + for (p=token; *p != '\0'; p++) { + if (*p == '(') { + openParen = p; + } + else if (*p == ')' && openParen) { + *openParen = '\0'; + break; + } + } + + /* + * Figure out what context we're in. If this is a class, + * then look up the variable in the class definition. + * If this is a namespace, then look up the variable in its + * varTable. Note that the normal Itcl_GetContext function + * returns an error if we're not in a class context, so we + * perform a similar function here, the hard way. + * + * TRICKY NOTE: If this is an array reference, we'll get + * the array variable as the variable name. We must be + * careful to add the index (everything from openParen + * onward) as well. + */ + if (Itcl_IsClassNamespace(contextNs)) { + contextClass = (ItclClass*)contextNs->clientData; + + entry = Tcl_FindHashEntry(&contextClass->resolveVars, token); + if (!entry) { + Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), + "variable \"", token, "\" not found in class \"", + contextClass->fullname, "\"", + (char*)NULL); + result = TCL_ERROR; + goto scopeCmdDone; + } + vlookup = (ItclVarLookup*)Tcl_GetHashValue(entry); + + if (vlookup->vdefn->member->flags & ITCL_COMMON) { + Tcl_Obj *resultPtr = Tcl_GetObjResult(interp); + Tcl_AppendToObj(resultPtr, vlookup->vdefn->member->fullname, -1); + if (openParen) { + *openParen = '('; + Tcl_AppendToObj(resultPtr, openParen, -1); + openParen = NULL; + } + result = TCL_OK; + goto scopeCmdDone; + } + + /* + * If this is not a common variable, then we better have + * an object context. Return the name "@itcl object variable". + */ + framePtr = _Tcl_GetCallFrame(interp, 0); + info = contextClass->info; + + entry = Tcl_FindHashEntry(&info->contextFrames, (char*)framePtr); + if (!entry) { + Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), + "can't scope variable \"", token, + "\": missing object context\"", + (char*)NULL); + result = TCL_ERROR; + goto scopeCmdDone; + } + contextObj = (ItclObject*)Tcl_GetHashValue(entry); + + Tcl_AppendElement(interp, "@itcl"); + + objPtr = Tcl_NewStringObj((char*)NULL, 0); + Tcl_IncrRefCount(objPtr); + Tcl_GetCommandFullName(interp, contextObj->accessCmd, objPtr); + Tcl_AppendElement(interp, Tcl_GetStringFromObj(objPtr, (int*)NULL)); + Tcl_DecrRefCount(objPtr); + + objPtr = Tcl_NewStringObj((char*)NULL, 0); + Tcl_IncrRefCount(objPtr); + Tcl_AppendToObj(objPtr, vlookup->vdefn->member->fullname, -1); + + if (openParen) { + *openParen = '('; + Tcl_AppendToObj(objPtr, openParen, -1); + openParen = NULL; + } + Tcl_AppendElement(interp, Tcl_GetStringFromObj(objPtr, (int*)NULL)); + Tcl_DecrRefCount(objPtr); + } + + /* + * We must be in an ordinary namespace context. Resolve + * the variable using Tcl_FindNamespaceVar. + * + * TRICKY NOTE: If this is an array reference, we'll get + * the array variable as the variable name. We must be + * careful to add the index (everything from openParen + * onward) as well. + */ + else { + Tcl_Obj *resultPtr = Tcl_GetObjResult(interp); + + var = Tcl_FindNamespaceVar(interp, token, contextNs, + TCL_NAMESPACE_ONLY); + + if (!var) { + Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), + "variable \"", token, "\" not found in namespace \"", + contextNs->fullName, "\"", + (char*)NULL); + result = TCL_ERROR; + goto scopeCmdDone; + } + + Tcl_GetVariableFullName(interp, var, resultPtr); + if (openParen) { + *openParen = '('; + Tcl_AppendToObj(resultPtr, openParen, -1); + openParen = NULL; + } + } + +scopeCmdDone: + if (openParen) { + *openParen = '('; + } + return result; +} + + +/* + * ------------------------------------------------------------------------ + * Itcl_CodeCmd() + * + * Invoked by Tcl whenever the user issues a "code" command to + * create a scoped command string. Handles the following syntax: + * + * code ?-namespace foo? arg ?arg arg ...? + * + * Unlike the scope command, the code command DOES NOT look for + * scoping information at the beginning of the command. So scopes + * will nest in the code command. + * + * The code command is similar to the "namespace code" command in + * Tcl, but it preserves the list structure of the input arguments, + * so it is a lot more useful. + * + * Returns TCL_OK/TCL_ERROR to indicate success/failure. + * ------------------------------------------------------------------------ + */ +/* ARGSUSED */ +int +Itcl_CodeCmd(dummy, interp, objc, objv) + ClientData dummy; /* unused */ + Tcl_Interp *interp; /* current interpreter */ + int objc; /* number of arguments */ + Tcl_Obj *CONST objv[]; /* argument objects */ +{ + Tcl_Namespace *contextNs = Tcl_GetCurrentNamespace(interp); + + int pos; + char *token; + Tcl_Obj *listPtr, *objPtr; + + /* + * Handle flags like "-namespace"... + */ + for (pos=1; pos < objc; pos++) { + token = Tcl_GetStringFromObj(objv[pos], (int*)NULL); + if (*token != '-') { + break; + } + + if (strcmp(token, "-namespace") == 0) { + if (objc == 2) { + Tcl_WrongNumArgs(interp, 1, objv, + "?-namespace name? command ?arg arg...?"); + return TCL_ERROR; + } else { + token = Tcl_GetStringFromObj(objv[pos+1], (int*)NULL); + contextNs = Tcl_FindNamespace(interp, token, + (Tcl_Namespace*)NULL, TCL_LEAVE_ERR_MSG); + + if (!contextNs) { + return TCL_ERROR; + } + pos++; + } + } + else if (strcmp(token, "--") == 0) { + pos++; + break; + } + else { + Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), + "bad option \"", token, "\": should be -namespace or --", + (char*)NULL); + return TCL_ERROR; + } + } + + if (objc < 2) { + Tcl_WrongNumArgs(interp, 1, objv, + "?-namespace name? command ?arg arg...?"); + return TCL_ERROR; + } + + /* + * Now construct a scoped command by integrating the + * current namespace context, and appending the remaining + * arguments AS A LIST... + */ + listPtr = Tcl_NewListObj(0, (Tcl_Obj**)NULL); + + Tcl_ListObjAppendElement(interp, listPtr, + Tcl_NewStringObj("namespace", -1)); + Tcl_ListObjAppendElement(interp, listPtr, + Tcl_NewStringObj("inscope", -1)); + + if (contextNs == Tcl_GetGlobalNamespace(interp)) { + objPtr = Tcl_NewStringObj("::", -1); + } else { + objPtr = Tcl_NewStringObj(contextNs->fullName, -1); + } + Tcl_ListObjAppendElement(interp, listPtr, objPtr); + + if (objc-pos == 1) { + objPtr = objv[pos]; + } else { + objPtr = Tcl_NewListObj(objc-pos, &objv[pos]); + } + Tcl_ListObjAppendElement(interp, listPtr, objPtr); + + Tcl_SetObjResult(interp, listPtr); + return TCL_OK; +} + + +/* + * ------------------------------------------------------------------------ + * Itcl_StubCreateCmd() + * + * Invoked by Tcl whenever the user issues a "stub create" command to + * create an autoloading stub for imported commands. Handles the + * following syntax: + * + * stub create + * + * Creates a command called . Executing this command will cause + * the real command to be autoloaded. + * ------------------------------------------------------------------------ + */ +int +Itcl_StubCreateCmd(clientData, interp, objc, objv) + ClientData clientData; /* not used */ + Tcl_Interp *interp; /* current interpreter */ + int objc; /* number of arguments */ + Tcl_Obj *CONST objv[]; /* argument objects */ +{ + char *cmdName; + Command *cmdPtr; + + if (objc != 2) { + Tcl_WrongNumArgs(interp, 1, objv, "name"); + return TCL_ERROR; + } + cmdName = Tcl_GetStringFromObj(objv[1], (int*)NULL); + + /* + * Create a stub command with the characteristic ItclDeleteStub + * procedure. That way, we can recognize this command later + * on as a stub. Save the cmd token as client data, so we can + * get the full name of this command later on. + */ + cmdPtr = (Command *) Tcl_CreateObjCommand(interp, cmdName, + ItclHandleStubCmd, (ClientData)NULL, + (Tcl_CmdDeleteProc*)ItclDeleteStub); + + cmdPtr->objClientData = (ClientData) cmdPtr; + + return TCL_OK; +} + + +/* + * ------------------------------------------------------------------------ + * Itcl_StubExistsCmd() + * + * Invoked by Tcl whenever the user issues a "stub exists" command to + * see if an existing command is an autoloading stub. Handles the + * following syntax: + * + * stub exists + * + * Looks for a command called and checks to see if it is an + * autoloading stub. Returns a boolean result. + * ------------------------------------------------------------------------ + */ +int +Itcl_StubExistsCmd(clientData, interp, objc, objv) + ClientData clientData; /* not used */ + Tcl_Interp *interp; /* current interpreter */ + int objc; /* number of arguments */ + Tcl_Obj *CONST objv[]; /* argument objects */ +{ + char *cmdName; + Tcl_Command cmd; + + if (objc != 2) { + Tcl_WrongNumArgs(interp, 1, objv, "name"); + return TCL_ERROR; + } + cmdName = Tcl_GetStringFromObj(objv[1], (int*)NULL); + + cmd = Tcl_FindCommand(interp, cmdName, (Tcl_Namespace*)NULL, 0); + + if (cmd != NULL && Itcl_IsStub(cmd)) { + Tcl_SetIntObj(Tcl_GetObjResult(interp), 1); + } else { + Tcl_SetIntObj(Tcl_GetObjResult(interp), 0); + } + return TCL_OK; +} + + +/* + * ------------------------------------------------------------------------ + * Itcl_IsStub() + * + * Checks the given Tcl command to see if it represents an autoloading + * stub created by the "stub create" command. Returns non-zero if + * the command is indeed a stub. + * ------------------------------------------------------------------------ + */ +int +Itcl_IsStub(cmd) + Tcl_Command cmd; /* command being tested */ +{ + Command *cmdPtr = (Command*)cmd; + + /* + * This may be an imported command, but don't try to get the + * original. Just check to see if this particular command + * is a stub. If we really want the original command, we'll + * find it at a higher level. + */ + if (cmdPtr->deleteProc == ItclDeleteStub) { + return 1; + } + return 0; +} + + +/* + * ------------------------------------------------------------------------ + * ItclHandleStubCmd() + * + * Invoked by Tcl to handle commands created by "stub create". + * Calls "auto_load" with the full name of the current command to + * trigger autoloading of the real implementation. Then, calls the + * command to handle its function. If successful, this command + * returns TCL_OK along with the result from the real implementation + * of this command. Otherwise, it returns TCL_ERROR, along with an + * error message in the interpreter. + * ------------------------------------------------------------------------ + */ +static int +ItclHandleStubCmd(clientData, interp, objc, objv) + ClientData clientData; /* command token for this stub */ + Tcl_Interp *interp; /* current interpreter */ + int objc; /* number of arguments */ + Tcl_Obj *CONST objv[]; /* argument objects */ +{ + Tcl_Command cmd = (Tcl_Command) clientData; + + int result, loaded; + char *cmdName; + int cmdlinec; + Tcl_Obj **cmdlinev; + Tcl_Obj *objAutoLoad[2], *objPtr, *cmdNamePtr, *cmdlinePtr; + + cmdNamePtr = Tcl_NewStringObj((char*)NULL, 0); + Tcl_GetCommandFullName(interp, cmd, cmdNamePtr); + Tcl_IncrRefCount(cmdNamePtr); + cmdName = Tcl_GetStringFromObj(cmdNamePtr, (int*)NULL); + + /* + * Try to autoload the real command for this stub. + */ + objAutoLoad[0] = Tcl_NewStringObj("::auto_load", -1); + Tcl_IncrRefCount(objAutoLoad[0]); + objAutoLoad[1] = cmdNamePtr; + Tcl_IncrRefCount(objAutoLoad[1]); + + result = Itcl_EvalArgs(interp, 2, objAutoLoad); + + Tcl_DecrRefCount(objAutoLoad[0]); + Tcl_DecrRefCount(objAutoLoad[1]); + + if (result != TCL_OK) { + Tcl_DecrRefCount(cmdNamePtr); + return TCL_ERROR; + } + + objPtr = Tcl_GetObjResult(interp); + result = Tcl_GetIntFromObj(interp, objPtr, &loaded); + if (result != TCL_OK || !loaded) { + Tcl_ResetResult(interp); + Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), + "can't autoload \"", cmdName, "\"", (char*)NULL); + Tcl_DecrRefCount(cmdNamePtr); + return TCL_ERROR; + } + + /* + * At this point, the real implementation has been loaded. + * Invoke the command again with the arguments passed in. + */ + cmdlinePtr = Itcl_CreateArgs(interp, cmdName, objc-1, objv+1); + + (void) Tcl_ListObjGetElements((Tcl_Interp*)NULL, cmdlinePtr, + &cmdlinec, &cmdlinev); + + Tcl_ResetResult(interp); + result = Itcl_EvalArgs(interp, cmdlinec, cmdlinev); + Tcl_DecrRefCount(cmdlinePtr); + + return result; +} + + +/* + * ------------------------------------------------------------------------ + * ItclDeleteStub() + * + * Invoked by Tcl whenever a stub command is deleted. This procedure + * does nothing, but its presence identifies a command as a stub. + * ------------------------------------------------------------------------ + */ +/* ARGSUSED */ +static void +ItclDeleteStub(cdata) + ClientData cdata; /* not used */ +{ + /* do nothing */ +}
itcl_cmds.c Property changes : Added: svn:executable ## -0,0 +1 ## +* \ No newline at end of property Index: itcl_migrate.c =================================================================== --- itcl_migrate.c (nonexistent) +++ itcl_migrate.c (revision 1765) @@ -0,0 +1,139 @@ +/* + * ------------------------------------------------------------------------ + * PACKAGE: [incr Tcl] + * DESCRIPTION: Object-Oriented Extensions to Tcl + * + * This file contains procedures that belong in the Tcl/Tk core. + * Hopefully, they'll migrate there soon. + * + * ======================================================================== + * AUTHOR: Michael J. McLennan + * Bell Labs Innovations for Lucent Technologies + * mmclennan@lucent.com + * http://www.tcltk.com/itcl + * + * RCS: $Id: itcl_migrate.c,v 1.1.1.1 2002-01-16 10:24:46 markom Exp $ + * ======================================================================== + * Copyright (c) 1993-1998 Lucent Technologies, Inc. + * ------------------------------------------------------------------------ + * See the file "license.terms" for information on usage and redistribution + * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + */ +#include "itclInt.h" + + +/* + *---------------------------------------------------------------------- + * + * _Tcl_GetCallFrame -- + * + * Checks the call stack and returns the call frame some number + * of levels up. It is often useful to know the invocation + * context for a command. + * + * Results: + * Returns a token for the call frame 0 or more levels up in + * the call stack. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ +Tcl_CallFrame* +_Tcl_GetCallFrame(interp, level) + Tcl_Interp *interp; /* interpreter being queried */ + int level; /* number of levels up in the call stack (>= 0) */ +{ + Interp *iPtr = (Interp*)interp; + CallFrame *framePtr; + + if (level < 0) { + panic("itcl: _Tcl_GetCallFrame called with bad number of levels"); + } + + framePtr = iPtr->varFramePtr; + while (framePtr && level > 0) { + framePtr = framePtr->callerVarPtr; + level--; + } + return (Tcl_CallFrame*)framePtr; +} + + +/* + *---------------------------------------------------------------------- + * + * _Tcl_ActivateCallFrame -- + * + * Makes an existing call frame the current frame on the + * call stack. Usually called in conjunction with + * _Tcl_GetCallFrame to simulate the effect of an "uplevel" + * command. + * + * Note that this procedure is different from Tcl_PushCallFrame, + * which adds a new call frame to the call stack. This procedure + * assumes that the call frame is already initialized, and it + * merely activates it on the call stack. + * + * Results: + * Returns a token for the call frame that was in effect before + * activating the new context. That call frame can be restored + * by calling _Tcl_ActivateCallFrame again. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ +Tcl_CallFrame* +_Tcl_ActivateCallFrame(interp, framePtr) + Tcl_Interp *interp; /* interpreter being queried */ + Tcl_CallFrame *framePtr; /* call frame to be activated */ +{ + Interp *iPtr = (Interp*)interp; + CallFrame *oldFramePtr; + + oldFramePtr = iPtr->varFramePtr; + iPtr->varFramePtr = (CallFrame *) framePtr; + + return (Tcl_CallFrame *) oldFramePtr; +} + +/* + *---------------------------------------------------------------------- + * + * _TclNewVar -- + * + * Create a new heap-allocated variable that will eventually be + * entered into a hashtable. + * + * Results: + * The return value is a pointer to the new variable structure. It is + * marked as a scalar variable (and not a link or array variable). Its + * value initially is NULL. The variable is not part of any hash table + * yet. Since it will be in a hashtable and not in a call frame, its + * name field is set NULL. It is initially marked as undefined. + * + * Side effects: + * Storage gets allocated. + * + *---------------------------------------------------------------------- + */ + +Var * +_TclNewVar() +{ + register Var *varPtr; + + varPtr = (Var *) ckalloc(sizeof(Var)); + varPtr->value.objPtr = NULL; + varPtr->name = NULL; + varPtr->nsPtr = NULL; + varPtr->hPtr = NULL; + varPtr->refCount = 0; + varPtr->tracePtr = NULL; + varPtr->searchPtr = NULL; + varPtr->flags = (VAR_SCALAR | VAR_UNDEFINED | VAR_IN_HASHTABLE); + return varPtr; +}
itcl_migrate.c Property changes : Added: svn:executable ## -0,0 +1 ## +* \ No newline at end of property Index: itcl_objects.c =================================================================== --- itcl_objects.c (nonexistent) +++ itcl_objects.c (revision 1765) @@ -0,0 +1,1208 @@ +/* + * ------------------------------------------------------------------------ + * PACKAGE: [incr Tcl] + * DESCRIPTION: Object-Oriented Extensions to Tcl + * + * [incr Tcl] provides object-oriented extensions to Tcl, much as + * C++ provides object-oriented extensions to C. It provides a means + * of encapsulating related procedures together with their shared data + * in a local namespace that is hidden from the outside world. It + * promotes code re-use through inheritance. More than anything else, + * it encourages better organization of Tcl applications through the + * object-oriented paradigm, leading to code that is easier to + * understand and maintain. + * + * This segment handles "objects" which are instantiated from class + * definitions. Objects contain public/protected/private data members + * from all classes in a derivation hierarchy. + * + * ======================================================================== + * AUTHOR: Michael J. McLennan + * Bell Labs Innovations for Lucent Technologies + * mmclennan@lucent.com + * http://www.tcltk.com/itcl + * + * RCS: $Id: itcl_objects.c,v 1.1.1.1 2002-01-16 10:24:46 markom Exp $ + * ======================================================================== + * Copyright (c) 1993-1998 Lucent Technologies, Inc. + * ------------------------------------------------------------------------ + * See the file "license.terms" for information on usage and redistribution + * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + */ +#include "itclInt.h" + +/* + * FORWARD DECLARATIONS + */ +static void ItclReportObjectUsage _ANSI_ARGS_((Tcl_Interp *interp, + ItclObject* obj)); + +static char* ItclTraceThisVar _ANSI_ARGS_((ClientData cdata, + Tcl_Interp *interp, char *name1, char *name2, int flags)); + +static void ItclDestroyObject _ANSI_ARGS_((ClientData cdata)); +static void ItclFreeObject _ANSI_ARGS_((char* cdata)); + +static int ItclDestructBase _ANSI_ARGS_((Tcl_Interp *interp, + ItclObject* obj, ItclClass* cdefn, int flags)); + +static void ItclCreateObjVar _ANSI_ARGS_((Tcl_Interp *interp, + ItclVarDefn* vdefn, ItclObject* obj)); + + +/* + * ------------------------------------------------------------------------ + * Itcl_CreateObject() + * + * Creates a new object instance belonging to the given class. + * Supports complex object names like "namesp::namesp::name" by + * following the namespace path and creating the object in the + * desired namespace. + * + * Automatically creates and initializes data members, including the + * built-in protected "this" variable containing the object name. + * Installs an access command in the current namespace, and invokes + * the constructor to initialize the object. + * + * If any errors are encountered, the object is destroyed and this + * procedure returns TCL_ERROR (along with an error message in the + * interpreter). Otherwise, it returns TCL_OK, along with a pointer + * to the new object data in roPtr. + * ------------------------------------------------------------------------ + */ +int +Itcl_CreateObject(interp, name, cdefn, objc, objv, roPtr) + Tcl_Interp *interp; /* interpreter mananging new object */ + char* name; /* name of new object */ + ItclClass *cdefn; /* class for new object */ + int objc; /* number of arguments */ + Tcl_Obj *CONST objv[]; /* argument objects */ + ItclObject **roPtr; /* returns: pointer to object data */ +{ + ItclClass *cdefnPtr = (ItclClass*)cdefn; + int result = TCL_OK; + + char *head, *tail; + Tcl_DString buffer, objName; + Tcl_Namespace *parentNs; + ItclContext context; + Tcl_Command cmd; + ItclObject *newObj; + ItclClass *cdPtr; + ItclVarDefn *vdefn; + ItclHierIter hier; + Tcl_HashEntry *entry; + Tcl_HashSearch place; + int newEntry; + Itcl_InterpState istate; + + /* + * If installing an object access command will clobber another + * command, signal an error. + */ + cmd = Tcl_FindCommand(interp, name, (Tcl_Namespace*)NULL, /* flags */ 0); + if (cmd != NULL && !Itcl_IsStub(cmd)) { + Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), + "command \"", name, "\" already exists in namespace \"", + Tcl_GetCurrentNamespace(interp)->fullName, "\"", + (char*)NULL); + return TCL_ERROR; + } + + /* + * Extract the namespace context and the simple object + * name for the new object. + */ + Itcl_ParseNamespPath(name, &buffer, &head, &tail); + if (head) { + parentNs = Itcl_FindClassNamespace(interp, head); + + if (!parentNs) { + Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), + "namespace \"", head, "\" not found in context \"", + Tcl_GetCurrentNamespace(interp)->fullName, "\"", + (char*)NULL); + Tcl_DStringFree(&buffer); + return TCL_ERROR; + } + } else { + parentNs = Tcl_GetCurrentNamespace(interp); + } + + Tcl_DStringInit(&objName); + if (parentNs != Tcl_GetGlobalNamespace(interp)) { + Tcl_DStringAppend(&objName, parentNs->fullName, -1); + } + Tcl_DStringAppend(&objName, "::", -1); + Tcl_DStringAppend(&objName, tail, -1); + + /* + * Create a new object and initialize it. + */ + newObj = (ItclObject*)ckalloc(sizeof(ItclObject)); + newObj->classDefn = cdefnPtr; + Itcl_PreserveData((ClientData)cdefnPtr); + + newObj->dataSize = cdefnPtr->numInstanceVars; + newObj->data = (Var**)ckalloc((unsigned)(newObj->dataSize*sizeof(Var*))); + + newObj->constructed = (Tcl_HashTable*)ckalloc(sizeof(Tcl_HashTable)); + Tcl_InitHashTable(newObj->constructed, TCL_STRING_KEYS); + newObj->destructed = NULL; + + /* + * Add a command to the current namespace with the object name. + * This is done before invoking the constructors so that the + * command can be used during construction to query info. + */ + Itcl_PreserveData((ClientData)newObj); + newObj->accessCmd = Tcl_CreateObjCommand(interp, + Tcl_DStringValue(&objName), Itcl_HandleInstance, + (ClientData)newObj, ItclDestroyObject); + + Itcl_PreserveData((ClientData)newObj); /* while we're using this... */ + Itcl_EventuallyFree((ClientData)newObj, ItclFreeObject); + + Tcl_DStringFree(&buffer); + Tcl_DStringFree(&objName); + + /* + * Install the class namespace and object context so that + * the object's data members can be initialized via simple + * "set" commands. + */ + if (Itcl_PushContext(interp, (ItclMember*)NULL, cdefnPtr, newObj, + &context) != TCL_OK) { + + return TCL_ERROR; + } + + Itcl_InitHierIter(&hier, cdefn); + + cdPtr = Itcl_AdvanceHierIter(&hier); + while (cdPtr != NULL) { + entry = Tcl_FirstHashEntry(&cdPtr->variables, &place); + while (entry) { + vdefn = (ItclVarDefn*)Tcl_GetHashValue(entry); + if ((vdefn->member->flags & ITCL_THIS_VAR) != 0) { + if (cdPtr == cdefnPtr) { + ItclCreateObjVar(interp, vdefn, newObj); + Tcl_SetVar2(interp, "this", (char*)NULL, "", 0); + Tcl_TraceVar2(interp, "this", (char*)NULL, + TCL_TRACE_READS|TCL_TRACE_WRITES, ItclTraceThisVar, + (ClientData)newObj); + } + } + else if ( (vdefn->member->flags & ITCL_COMMON) == 0) { + ItclCreateObjVar(interp, vdefn, newObj); + } + entry = Tcl_NextHashEntry(&place); + } + cdPtr = Itcl_AdvanceHierIter(&hier); + } + Itcl_DeleteHierIter(&hier); + + Itcl_PopContext(interp, &context); /* back to calling context */ + + /* + * Now construct the object. Look for a constructor in the + * most-specific class, and if there is one, invoke it. + * This will cause a chain reaction, making sure that all + * base classes constructors are invoked as well, in order + * from least- to most-specific. Any constructors that are + * not called out explicitly in "initCode" code fragments are + * invoked implicitly without arguments. + */ + result = Itcl_InvokeMethodIfExists(interp, "constructor", + cdefn, newObj, objc, objv); + + /* + * If there is no constructor, construct the base classes + * in case they have constructors. This will cause the + * same chain reaction. + */ + if (!Tcl_FindHashEntry(&cdefn->functions, "constructor")) { + result = Itcl_ConstructBase(interp, newObj, cdefn); + } + + /* + * If construction failed, then delete the object access + * command. This will destruct the object and delete the + * object data. Be careful to save and restore the interpreter + * state, since the destructors may generate errors of their own. + */ + if (result != TCL_OK) { + istate = Itcl_SaveInterpState(interp, result); + if (newObj->accessCmd != NULL) { + Tcl_DeleteCommandFromToken(interp, newObj->accessCmd); + newObj->accessCmd = NULL; + } + result = Itcl_RestoreInterpState(interp, istate); + } + + /* + * At this point, the object is fully constructed. + * Destroy the "constructed" table in the object data, since + * it is no longer needed. + */ + Tcl_DeleteHashTable(newObj->constructed); + ckfree((char*)newObj->constructed); + newObj->constructed = NULL; + + /* + * Add it to the list of all known objects. The only + * tricky thing to watch out for is the case where the + * object deleted itself inside its own constructor. + * In that case, we don't want to add the object to + * the list of valid objects. We can determine that + * the object deleted itself by checking to see if its + * accessCmd member is NULL. + */ + if ((result == TCL_OK) && (newObj->accessCmd != NULL)) { + entry = Tcl_CreateHashEntry(&cdefnPtr->info->objects, + (char*)newObj->accessCmd, &newEntry); + + Tcl_SetHashValue(entry, (ClientData)newObj); + } + + /* + * Release the object. If it was destructed above, it will + * die at this point. + */ + Itcl_ReleaseData((ClientData)newObj); + + *roPtr = newObj; + return result; +} + + +/* + * ------------------------------------------------------------------------ + * Itcl_DeleteObject() + * + * Attempts to delete an object by invoking its destructor. + * + * If the destructor is successful, then the object is deleted by + * removing its access command, and this procedure returns TCL_OK. + * Otherwise, the object will remain alive, and this procedure + * returns TCL_ERROR (along with an error message in the interpreter). + * ------------------------------------------------------------------------ + */ +int +Itcl_DeleteObject(interp, contextObj) + Tcl_Interp *interp; /* interpreter mananging object */ + ItclObject *contextObj; /* object to be deleted */ +{ + ItclClass *cdefnPtr = (ItclClass*)contextObj->classDefn; + + Tcl_HashEntry *entry; + Command *cmdPtr; + + Itcl_PreserveData((ClientData)contextObj); + + /* + * Invoke the object's destructors. + */ + if (Itcl_DestructObject(interp, contextObj, 0) != TCL_OK) { + Itcl_ReleaseData((ClientData)contextObj); + return TCL_ERROR; + } + + /* + * Remove the object from the global list. + */ + entry = Tcl_FindHashEntry(&cdefnPtr->info->objects, + (char*)contextObj->accessCmd); + + if (entry) { + Tcl_DeleteHashEntry(entry); + } + + /* + * Change the object's access command so that it can be + * safely deleted without attempting to destruct the object + * again. Then delete the access command. If this is + * the last use of the object data, the object will die here. + */ + cmdPtr = (Command*)contextObj->accessCmd; + cmdPtr->deleteProc = Itcl_ReleaseData; + + Tcl_DeleteCommandFromToken(interp, contextObj->accessCmd); + contextObj->accessCmd = NULL; + + Itcl_ReleaseData((ClientData)contextObj); /* object should die here */ + + return TCL_OK; +} + + +/* + * ------------------------------------------------------------------------ + * Itcl_DestructObject() + * + * Invokes the destructor for a particular object. Usually invoked + * by Itcl_DeleteObject() or Itcl_DestroyObject() as a part of the + * object destruction process. If the ITCL_IGNORE_ERRS flag is + * included, all destructors are invoked even if errors are + * encountered, and the result will always be TCL_OK. + * + * Returns TCL_OK on success, or TCL_ERROR (along with an error + * message in the interpreter) if anything goes wrong. + * ------------------------------------------------------------------------ + */ +int +Itcl_DestructObject(interp, contextObj, flags) + Tcl_Interp *interp; /* interpreter mananging new object */ + ItclObject *contextObj; /* object to be destructed */ + int flags; /* flags: ITCL_IGNORE_ERRS */ +{ + int result; + + /* + * If there is a "destructed" table, then this object is already + * being destructed. Flag an error, unless errors are being + * ignored. + */ + if (contextObj->destructed) { + if ((flags & ITCL_IGNORE_ERRS) == 0) { + Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), + "can't delete an object while it is being destructed", + (char*)NULL); + return TCL_ERROR; + } + return TCL_OK; + } + + /* + * Create a "destructed" table to keep track of which destructors + * have been invoked. This is used in ItclDestructBase to make + * sure that all base class destructors have been called, + * explicitly or implicitly. + */ + contextObj->destructed = (Tcl_HashTable*)ckalloc(sizeof(Tcl_HashTable)); + Tcl_InitHashTable(contextObj->destructed, TCL_STRING_KEYS); + + /* + * Destruct the object starting from the most-specific class. + * If all goes well, return the null string as the result. + */ + result = ItclDestructBase(interp, contextObj, contextObj->classDefn, flags); + + if (result == TCL_OK) { + Tcl_ResetResult(interp); + } + + Tcl_DeleteHashTable(contextObj->destructed); + ckfree((char*)contextObj->destructed); + contextObj->destructed = NULL; + + return result; +} + +/* + * ------------------------------------------------------------------------ + * ItclDestructBase() + * + * Invoked by Itcl_DestructObject() to recursively destruct an object + * from the specified class level. Finds and invokes the destructor + * for the specified class, and then recursively destructs all base + * classes. If the ITCL_IGNORE_ERRS flag is included, all destructors + * are invoked even if errors are encountered, and the result will + * always be TCL_OK. + * + * Returns TCL_OK on success, or TCL_ERROR (along with an error message + * in interp->result) on error. + * ------------------------------------------------------------------------ + */ +static int +ItclDestructBase(interp, contextObj, contextClass, flags) + Tcl_Interp *interp; /* interpreter */ + ItclObject *contextObj; /* object being destructed */ + ItclClass *contextClass; /* current class being destructed */ + int flags; /* flags: ITCL_IGNORE_ERRS */ +{ + int result; + Itcl_ListElem *elem; + ItclClass *cdefn; + + /* + * Look for a destructor in this class, and if found, + * invoke it. + */ + if (!Tcl_FindHashEntry(contextObj->destructed, contextClass->name)) { + + result = Itcl_InvokeMethodIfExists(interp, "destructor", + contextClass, contextObj, 0, (Tcl_Obj* CONST*)NULL); + + if (result != TCL_OK) { + return TCL_ERROR; + } + } + + /* + * Scan through the list of base classes recursively and destruct + * them. Traverse the list in normal order, so that we destruct + * from most- to least-specific. + */ + elem = Itcl_FirstListElem(&contextClass->bases); + while (elem) { + cdefn = (ItclClass*)Itcl_GetListValue(elem); + + if (ItclDestructBase(interp, contextObj, cdefn, flags) != TCL_OK) { + return TCL_ERROR; + } + elem = Itcl_NextListElem(elem); + } + + /* + * Throw away any result from the destructors and return. + */ + Tcl_ResetResult(interp); + return TCL_OK; +} + + +/* + * ------------------------------------------------------------------------ + * Itcl_FindObject() + * + * Searches for an object with the specified name, which have + * namespace scope qualifiers like "namesp::namesp::name", or may + * be a scoped value such as "namespace inscope ::foo obj". + * + * If an error is encountered, this procedure returns TCL_ERROR + * along with an error message in the interpreter. Otherwise, it + * returns TCL_OK. If an object was found, "roPtr" returns a + * pointer to the object data. Otherwise, it returns NULL. + * ------------------------------------------------------------------------ + */ +int +Itcl_FindObject(interp, name, roPtr) + Tcl_Interp *interp; /* interpreter containing this object */ + char *name; /* name of the object */ + ItclObject **roPtr; /* returns: object data or NULL */ +{ + Tcl_Namespace *contextNs = NULL; + + char *cmdName; + Tcl_Command cmd; + Command *cmdPtr; + + /* + * The object name may be a scoped value of the form + * "namespace inscope ". If it is, + * decode it. + */ + if (Itcl_DecodeScopedCommand(interp, name, &contextNs, &cmdName) + != TCL_OK) { + return TCL_ERROR; + } + + /* + * Look for the object's access command, and see if it has + * the appropriate command handler. + */ + cmd = Tcl_FindCommand(interp, cmdName, contextNs, /* flags */ 0); + if (cmd != NULL && Itcl_IsObject(cmd)) { + cmdPtr = (Command*)cmd; + *roPtr = (ItclObject*)cmdPtr->objClientData; + } + else { + *roPtr = NULL; + } + + if (cmdName != name) { + ckfree(cmdName); + } + return TCL_OK; +} + + +/* + * ------------------------------------------------------------------------ + * Itcl_IsObject() + * + * Checks the given Tcl command to see if it represents an itcl object. + * Returns non-zero if the command is associated with an object. + * ------------------------------------------------------------------------ + */ +int +Itcl_IsObject(cmd) + Tcl_Command cmd; /* command being tested */ +{ + Command *cmdPtr = (Command*)cmd; + + if (cmdPtr->deleteProc == ItclDestroyObject) { + return 1; + } + + /* + * This may be an imported command. Try to get the real + * command and see if it represents an object. + */ + cmdPtr = (Command*)TclGetOriginalCommand(cmd); + if (cmdPtr && cmdPtr->deleteProc == ItclDestroyObject) { + return 1; + } + return 0; +} + + +/* + * ------------------------------------------------------------------------ + * Itcl_ObjectIsa() + * + * Checks to see if an object belongs to the given class. An object + * "is-a" member of the class if the class appears anywhere in its + * inheritance hierarchy. Returns non-zero if the object belongs to + * the class, and zero otherwise. + * ------------------------------------------------------------------------ + */ +int +Itcl_ObjectIsa(contextObj, cdefn) + ItclObject *contextObj; /* object being tested */ + ItclClass *cdefn; /* class to test for "is-a" relationship */ +{ + Tcl_HashEntry *entry; + entry = Tcl_FindHashEntry(&contextObj->classDefn->heritage, (char*)cdefn); + return (entry != NULL); +} + + +/* + * ------------------------------------------------------------------------ + * Itcl_HandleInstance() + * + * Invoked by Tcl whenever the user issues a command associated with + * an object instance. Handles the following syntax: + * + * ... + * + * ------------------------------------------------------------------------ + */ +int +Itcl_HandleInstance(clientData, interp, objc, objv) + ClientData clientData; /* object definition */ + Tcl_Interp *interp; /* current interpreter */ + int objc; /* number of arguments */ + Tcl_Obj *CONST objv[]; /* argument objects */ +{ + ItclObject *contextObj = (ItclObject*)clientData; + + int result; + char *token; + Tcl_HashEntry *entry; + ItclMemberFunc *mfunc; + ItclObjectInfo *info; + ItclContext context; + CallFrame *framePtr; + + if (objc < 2) { + Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), + "wrong # args: should be one of...", + (char*)NULL); + ItclReportObjectUsage(interp, contextObj); + return TCL_ERROR; + } + + /* + * Make sure that the specified operation is really an + * object method, and it is accessible. If not, return usage + * information for the object. + */ + token = Tcl_GetStringFromObj(objv[1], (int*)NULL); + mfunc = NULL; + + entry = Tcl_FindHashEntry(&contextObj->classDefn->resolveCmds, token); + if (entry) { + mfunc = (ItclMemberFunc*)Tcl_GetHashValue(entry); + + if ((mfunc->member->flags & ITCL_COMMON) != 0) { + mfunc = NULL; + } + else if (mfunc->member->protection != ITCL_PUBLIC) { + Tcl_Namespace *contextNs = Itcl_GetTrueNamespace(interp, + mfunc->member->classDefn->info); + + if (!Itcl_CanAccessFunc(mfunc, contextNs)) { + mfunc = NULL; + } + } + } + + if ( !mfunc && (*token != 'i' || strcmp(token,"info") != 0) ) { + Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), + "bad option \"", token, "\": should be one of...", + (char*)NULL); + ItclReportObjectUsage(interp, contextObj); + return TCL_ERROR; + } + + /* + * Install an object context and invoke the method. + * + * TRICKY NOTE: We need to pass the object context into the + * method, but activating the context here puts us one level + * down, and when the method is called, it will activate its + * own context, putting us another level down. If anyone + * were to execute an "uplevel" command in the method, they + * would notice the extra call frame. So we mark this frame + * as "transparent" and Itcl_EvalMemberCode will automatically + * do an "uplevel" operation to correct the problem. + */ + info = contextObj->classDefn->info; + + if (Itcl_PushContext(interp, (ItclMember*)NULL, contextObj->classDefn, + contextObj, &context) != TCL_OK) { + + return TCL_ERROR; + } + + framePtr = &context.frame; + Itcl_PushStack((ClientData)framePtr, &info->transparentFrames); + + result = Itcl_EvalArgs(interp, objc-1, objv+1); + + Itcl_PopStack(&info->transparentFrames); + Itcl_PopContext(interp, &context); + + return result; +} + + +/* + * ------------------------------------------------------------------------ + * Itcl_GetInstanceVar() + * + * Returns the current value for an object data member. The member + * name is interpreted with respect to the given class scope, which + * is usually the most-specific class for the object. + * + * If successful, this procedure returns a pointer to a string value + * which remains alive until the variable changes it value. If + * anything goes wrong, this returns NULL. + * ------------------------------------------------------------------------ + */ +char* +Itcl_GetInstanceVar(interp, name, contextObj, contextClass) + Tcl_Interp *interp; /* current interpreter */ + char *name; /* name of desired instance variable */ + ItclObject *contextObj; /* current object */ + ItclClass *contextClass; /* name is interpreted in this scope */ +{ + ItclContext context; + char *val; + + /* + * Make sure that the current namespace context includes an + * object that is being manipulated. + */ + if (contextObj == NULL) { + Tcl_ResetResult(interp); + Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), + "cannot access object-specific info without an object context", + (char*)NULL); + return NULL; + } + + /* + * Install the object context and access the data member + * like any other variable. + */ + if (Itcl_PushContext(interp, (ItclMember*)NULL, contextClass, + contextObj, &context) != TCL_OK) { + + return NULL; + } + + val = Tcl_GetVar2(interp, name, (char*)NULL, TCL_LEAVE_ERR_MSG); + Itcl_PopContext(interp, &context); + + return val; +} + + +/* + * ------------------------------------------------------------------------ + * ItclReportObjectUsage() + * + * Appends information to the given interp summarizing the usage + * for all of the methods available for this object. Useful when + * reporting errors in Itcl_HandleInstance(). + * ------------------------------------------------------------------------ + */ +static void +ItclReportObjectUsage(interp, contextObj) + Tcl_Interp *interp; /* current interpreter */ + ItclObject *contextObj; /* current object */ +{ + ItclClass *cdefnPtr = (ItclClass*)contextObj->classDefn; + int ignore = ITCL_CONSTRUCTOR | ITCL_DESTRUCTOR | ITCL_COMMON; + + int cmp; + char *name; + Itcl_List cmdList; + Itcl_ListElem *elem; + Tcl_HashEntry *entry; + Tcl_HashSearch place; + ItclMemberFunc *mfunc, *cmpDefn; + Tcl_Obj *resultPtr; + + /* + * Scan through all methods in the virtual table and sort + * them in alphabetical order. Report only the methods + * that have simple names (no ::'s) and are accessible. + */ + Itcl_InitList(&cmdList); + entry = Tcl_FirstHashEntry(&cdefnPtr->resolveCmds, &place); + while (entry) { + name = Tcl_GetHashKey(&cdefnPtr->resolveCmds, entry); + mfunc = (ItclMemberFunc*)Tcl_GetHashValue(entry); + + if (strstr(name,"::") || (mfunc->member->flags & ignore) != 0) { + mfunc = NULL; + } + else if (mfunc->member->protection != ITCL_PUBLIC) { + Tcl_Namespace *contextNs = Itcl_GetTrueNamespace(interp, + mfunc->member->classDefn->info); + + if (!Itcl_CanAccessFunc(mfunc, contextNs)) { + mfunc = NULL; + } + } + + if (mfunc) { + elem = Itcl_FirstListElem(&cmdList); + while (elem) { + cmpDefn = (ItclMemberFunc*)Itcl_GetListValue(elem); + cmp = strcmp(mfunc->member->name, cmpDefn->member->name); + if (cmp < 0) { + Itcl_InsertListElem(elem, (ClientData)mfunc); + mfunc = NULL; + break; + } + else if (cmp == 0) { + mfunc = NULL; + break; + } + elem = Itcl_NextListElem(elem); + } + if (mfunc) { + Itcl_AppendList(&cmdList, (ClientData)mfunc); + } + } + entry = Tcl_NextHashEntry(&place); + } + + /* + * Add a series of statements showing usage info. + */ + resultPtr = Tcl_GetObjResult(interp); + elem = Itcl_FirstListElem(&cmdList); + while (elem) { + mfunc = (ItclMemberFunc*)Itcl_GetListValue(elem); + Tcl_AppendToObj(resultPtr, "\n ", -1); + Itcl_GetMemberFuncUsage(mfunc, contextObj, resultPtr); + + elem = Itcl_NextListElem(elem); + } + Itcl_DeleteList(&cmdList); +} + + +/* + * ------------------------------------------------------------------------ + * ItclTraceThisVar() + * + * Invoked to handle read/write traces on the "this" variable built + * into each object. + * + * On read, this procedure updates the "this" variable to contain the + * current object name. This is done dynamically, since an object's + * identity can change if its access command is renamed. + * + * On write, this procedure returns an error string, warning that + * the "this" variable cannot be set. + * ------------------------------------------------------------------------ + */ +/* ARGSUSED */ +static char* +ItclTraceThisVar(cdata, interp, name1, name2, flags) + ClientData cdata; /* object instance data */ + Tcl_Interp *interp; /* interpreter managing this variable */ + char *name1; /* variable name */ + char *name2; /* unused */ + int flags; /* flags indicating read/write */ +{ + ItclObject *contextObj = (ItclObject*)cdata; + char *objName; + Tcl_Obj *objPtr; + + /* + * Handle read traces on "this" + */ + if ((flags & TCL_TRACE_READS) != 0) { + objPtr = Tcl_NewStringObj("", -1); + Tcl_IncrRefCount(objPtr); + + if (contextObj->accessCmd) { + Tcl_GetCommandFullName(contextObj->classDefn->interp, + contextObj->accessCmd, objPtr); + } + + objName = Tcl_GetStringFromObj(objPtr, (int*)NULL); + Tcl_SetVar(interp, name1, objName, 0); + + Tcl_DecrRefCount(objPtr); + return NULL; + } + + /* + * Handle write traces on "this" + */ + if ((flags & TCL_TRACE_WRITES) != 0) { + return "variable \"this\" cannot be modified"; + } + return NULL; +} + + +/* + * ------------------------------------------------------------------------ + * ItclDestroyObject() + * + * Invoked when the object access command is deleted to implicitly + * destroy the object. Invokes the object's destructors, ignoring + * any errors encountered along the way. Removes the object from + * the list of all known objects and releases the access command's + * claim to the object data. + * + * Note that the usual way to delete an object is via Itcl_DeleteObject(). + * This procedure is provided as a back-up, to handle the case when + * an object is deleted by removing its access command. + * ------------------------------------------------------------------------ + */ +static void +ItclDestroyObject(cdata) + ClientData cdata; /* object instance data */ +{ + ItclObject *contextObj = (ItclObject*)cdata; + ItclClass *cdefnPtr = (ItclClass*)contextObj->classDefn; + Tcl_HashEntry *entry; + Itcl_InterpState istate; + + /* + * Attempt to destruct the object, but ignore any errors. + */ + istate = Itcl_SaveInterpState(cdefnPtr->interp, 0); + Itcl_DestructObject(cdefnPtr->interp, contextObj, ITCL_IGNORE_ERRS); + Itcl_RestoreInterpState(cdefnPtr->interp, istate); + + /* + * Now, remove the object from the global object list. + * We're careful to do this here, after calling the destructors. + * Once the access command is nulled out, the "this" variable + * won't work properly. + */ + if (contextObj->accessCmd) { + entry = Tcl_FindHashEntry(&cdefnPtr->info->objects, + (char*)contextObj->accessCmd); + + if (entry) { + Tcl_DeleteHashEntry(entry); + } + contextObj->accessCmd = NULL; + } + + Itcl_ReleaseData((ClientData)contextObj); +} + + +/* + * ------------------------------------------------------------------------ + * ItclFreeObject() + * + * Deletes all instance variables and frees all memory associated with + * the given object instance. This is usually invoked automatically + * by Itcl_ReleaseData(), when an object's data is no longer being used. + * ------------------------------------------------------------------------ + */ +static void +ItclFreeObject(cdata) + char* cdata; /* object instance data */ +{ + ItclObject *contextObj = (ItclObject*)cdata; + Tcl_Interp *interp = contextObj->classDefn->interp; + + int i; + ItclClass *cdPtr; + ItclHierIter hier; + Tcl_HashSearch place; + Tcl_HashEntry *entry; + ItclVarDefn *vdefn; + ItclContext context; + Itcl_InterpState istate; + + /* + * Install the class namespace and object context so that + * the object's data members can be destroyed via simple + * "unset" commands. This makes sure that traces work properly + * and all memory gets cleaned up. + * + * NOTE: Be careful to save and restore the interpreter state. + * Data can get freed in the middle of any operation, and + * we can't affort to clobber the interpreter with any errors + * from below. + */ + istate = Itcl_SaveInterpState(interp, 0); + + /* + * Scan through all object-specific data members and destroy the + * actual variables that maintain the object state. Do this + * by unsetting each variable, so that traces are fired off + * correctly. Make sure that the built-in "this" variable is + * only destroyed once. Also, be careful to activate the + * namespace for each class, so that private variables can + * be accessed. + */ + Itcl_InitHierIter(&hier, contextObj->classDefn); + cdPtr = Itcl_AdvanceHierIter(&hier); + while (cdPtr != NULL) { + + if (Itcl_PushContext(interp, (ItclMember*)NULL, cdPtr, + contextObj, &context) == TCL_OK) { + + entry = Tcl_FirstHashEntry(&cdPtr->variables, &place); + while (entry) { + vdefn = (ItclVarDefn*)Tcl_GetHashValue(entry); + if ((vdefn->member->flags & ITCL_THIS_VAR) != 0) { + if (cdPtr == contextObj->classDefn) { + Tcl_UnsetVar2(interp, vdefn->member->fullname, + (char*)NULL, 0); + } + } + else if ((vdefn->member->flags & ITCL_COMMON) == 0) { + Tcl_UnsetVar2(interp, vdefn->member->fullname, + (char*)NULL, 0); + } + entry = Tcl_NextHashEntry(&place); + } + Itcl_PopContext(interp, &context); + } + + cdPtr = Itcl_AdvanceHierIter(&hier); + } + Itcl_DeleteHierIter(&hier); + + /* + * Free the memory associated with object-specific variables. + * For normal variables this would be done automatically by + * CleanupVar() when the variable is unset. But object-specific + * variables are protected by an extra reference count, and they + * must be deleted explicitly here. + */ + for (i=0; i < contextObj->dataSize; i++) { + if (contextObj->data[i]) { + ckfree((char*)contextObj->data[i]); + } + } + + Itcl_RestoreInterpState(interp, istate); + + /* + * Free any remaining memory associated with the object. + */ + ckfree((char*)contextObj->data); + + if (contextObj->constructed) { + Tcl_DeleteHashTable(contextObj->constructed); + ckfree((char*)contextObj->constructed); + } + if (contextObj->destructed) { + Tcl_DeleteHashTable(contextObj->destructed); + ckfree((char*)contextObj->destructed); + } + Itcl_ReleaseData((ClientData)contextObj->classDefn); + + ckfree((char*)contextObj); +} + + +/* + * ------------------------------------------------------------------------ + * ItclCreateObjVar() + * + * Creates one variable acting as a data member for a specific + * object. Initializes the variable according to its definition, + * and sets up its reference count so that it cannot be deleted + * by ordinary means. Installs the new variable directly into + * the data array for the specified object. + * ------------------------------------------------------------------------ + */ +static void +ItclCreateObjVar(interp, vdefn, contextObj) + Tcl_Interp* interp; /* interpreter managing this object */ + ItclVarDefn* vdefn; /* variable definition */ + ItclObject* contextObj; /* object being updated */ +{ + Var *varPtr; + Tcl_HashEntry *entry; + ItclVarLookup *vlookup; + ItclContext context; + + varPtr = _TclNewVar(); + varPtr->name = vdefn->member->name; + varPtr->nsPtr = (Namespace*)vdefn->member->classDefn->namesp; + + /* + * NOTE: Tcl reports a "dangling upvar" error for variables + * with a null "hPtr" field. Put something non-zero + * in here to keep Tcl_SetVar2() happy. The only time + * this field is really used is it remove a variable + * from the hash table that contains it in CleanupVar, + * but since these variables are protected by their + * higher refCount, they will not be deleted by CleanupVar + * anyway. These variables are unset and removed in + * ItclFreeObject(). + */ + varPtr->hPtr = (Tcl_HashEntry*)0x1; + varPtr->refCount = 1; /* protect from being deleted */ + + /* + * Install the new variable in the object's data array. + * Look up the appropriate index for the object using + * the data table in the class definition. + */ + entry = Tcl_FindHashEntry(&contextObj->classDefn->resolveVars, + vdefn->member->fullname); + + if (entry) { + vlookup = (ItclVarLookup*)Tcl_GetHashValue(entry); + contextObj->data[vlookup->var.index] = varPtr; + } + + /* + * If this variable has an initial value, initialize it + * here using a "set" command. + * + * TRICKY NOTE: We push an object context for the class that + * owns the variable, so that we don't have any trouble + * accessing it. + */ + if (vdefn->init) { + if (Itcl_PushContext(interp, (ItclMember*)NULL, + vdefn->member->classDefn, contextObj, &context) == TCL_OK) { + + Tcl_SetVar2(interp, vdefn->member->fullname, + (char*)NULL, vdefn->init, 0); + Itcl_PopContext(interp, &context); + } + } +} + + +/* + * ------------------------------------------------------------------------ + * Itcl_ScopedVarResolver() + * + * This procedure is installed to handle variable resolution throughout + * an entire interpreter. It looks for scoped variable references of + * the form: + * + * @itcl ::namesp::namesp::object variable + * + * If a reference like this is recognized, this procedure finds the + * desired variable in the object and returns the variable, along with + * the status code TCL_OK. If the variable does not start with + * "@itcl", this procedure returns TCL_CONTINUE, and variable + * resolution continues using the normal rules. If anything goes + * wrong, this procedure returns TCL_ERROR, and access to the + * variable is denied. + * ------------------------------------------------------------------------ + */ +int +Itcl_ScopedVarResolver(interp, name, contextNs, flags, rPtr) + Tcl_Interp *interp; /* current interpreter */ + char *name; /* variable name being resolved */ + Tcl_Namespace *contextNs; /* current namespace context */ + int flags; /* TCL_LEAVE_ERR_MSG => leave error message */ + Tcl_Var *rPtr; /* returns: resolved variable */ +{ + int namec; + char **namev; + Tcl_Interp *errs; + Tcl_CmdInfo cmdInfo; + ItclObject *contextObj; + ItclVarLookup *vlookup; + Tcl_HashEntry *entry; + + /* + * See if the variable starts with "@itcl". If not, then + * let the variable resolution process continue. + */ + if (*name != '@' || strncmp(name, "@itcl", 5) != 0) { + return TCL_CONTINUE; + } + + /* + * Break the variable name into parts and extract the object + * name and the variable name. + */ + if (flags & TCL_LEAVE_ERR_MSG) { + errs = interp; + } else { + errs = NULL; + } + + if (Tcl_SplitList(errs, name, &namec, &namev) != TCL_OK) { + return TCL_ERROR; + } + if (namec != 3) { + if (errs) { + Tcl_AppendStringsToObj(Tcl_GetObjResult(errs), + "scoped variable \"", name, "\" is malformed: ", + "should be: @itcl object variable", + (char*)NULL); + } + ckfree((char*)namev); + return TCL_ERROR; + } + + /* + * Look for the command representing the object and extract + * the object context. + */ + if (!Tcl_GetCommandInfo(interp, namev[1], &cmdInfo)) { + if (errs) { + Tcl_AppendStringsToObj(Tcl_GetObjResult(errs), + "can't resolve scoped variable \"", name, "\": ", + "can't find object ", namev[1], + (char*)NULL); + } + ckfree((char*)namev); + return TCL_ERROR; + } + contextObj = (ItclObject*)cmdInfo.objClientData; + + /* + * Resolve the variable with respect to the most-specific + * class definition. + */ + entry = Tcl_FindHashEntry(&contextObj->classDefn->resolveVars, namev[2]); + if (!entry) { + if (errs) { + Tcl_AppendStringsToObj(Tcl_GetObjResult(errs), + "can't resolve scoped variable \"", name, "\": ", + "no such data member ", namev[2], + (char*)NULL); + } + ckfree((char*)namev); + return TCL_ERROR; + } + + vlookup = (ItclVarLookup*)Tcl_GetHashValue(entry); + *rPtr = (Tcl_Var) contextObj->data[vlookup->var.index]; + + ckfree((char*)namev); + return TCL_OK; +}
itcl_objects.c Property changes : Added: svn:executable ## -0,0 +1 ## +* \ No newline at end of property Index: itcl_ensemble.c =================================================================== --- itcl_ensemble.c (nonexistent) +++ itcl_ensemble.c (revision 1765) @@ -0,0 +1,2248 @@ +/* + * ------------------------------------------------------------------------ + * PACKAGE: [incr Tcl] + * DESCRIPTION: Object-Oriented Extensions to Tcl + * + * [incr Tcl] provides object-oriented extensions to Tcl, much as + * C++ provides object-oriented extensions to C. It provides a means + * of encapsulating related procedures together with their shared data + * in a local namespace that is hidden from the outside world. It + * promotes code re-use through inheritance. More than anything else, + * it encourages better organization of Tcl applications through the + * object-oriented paradigm, leading to code that is easier to + * understand and maintain. + * + * This part handles ensembles, which support compound commands in Tcl. + * The usual "info" command is an ensemble with parts like "info body" + * and "info globals". Extension developers can extend commands like + * "info" by adding their own parts to the ensemble. + * + * ======================================================================== + * AUTHOR: Michael J. McLennan + * Bell Labs Innovations for Lucent Technologies + * mmclennan@lucent.com + * http://www.tcltk.com/itcl + * + * RCS: $Id: itcl_ensemble.c,v 1.1.1.1 2002-01-16 10:24:46 markom Exp $ + * ======================================================================== + * Copyright (c) 1993-1998 Lucent Technologies, Inc. + * ------------------------------------------------------------------------ + * See the file "license.terms" for information on usage and redistribution + * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + */ +#include "itclInt.h" + +/* + * Data used to represent an ensemble: + */ +struct Ensemble; +typedef struct EnsemblePart { + char *name; /* name of this part */ + int minChars; /* chars needed to uniquely identify part */ + Command *cmdPtr; /* command handling this part */ + char *usage; /* usage string describing syntax */ + struct Ensemble* ensemble; /* ensemble containing this part */ +} EnsemblePart; + +/* + * Data used to represent an ensemble: + */ +typedef struct Ensemble { + Tcl_Interp *interp; /* interpreter containing this ensemble */ + EnsemblePart **parts; /* list of parts in this ensemble */ + int numParts; /* number of parts in part list */ + int maxParts; /* current size of parts list */ + Tcl_Command cmd; /* command representing this ensemble */ + EnsemblePart* parent; /* parent part for sub-ensembles + * NULL => toplevel ensemble */ +} Ensemble; + +/* + * Data shared by ensemble access commands and ensemble parser: + */ +typedef struct EnsembleParser { + Tcl_Interp* master; /* master interp containing ensembles */ + Tcl_Interp* parser; /* slave interp for parsing */ + Ensemble* ensData; /* add parts to this ensemble */ +} EnsembleParser; + +/* + * Declarations for local procedures to this file: + */ +static void FreeEnsInvocInternalRep _ANSI_ARGS_((Tcl_Obj *objPtr)); +static void DupEnsInvocInternalRep _ANSI_ARGS_((Tcl_Obj *objPtr, + Tcl_Obj *copyPtr)); +static void UpdateStringOfEnsInvoc _ANSI_ARGS_((Tcl_Obj *objPtr)); +static int SetEnsInvocFromAny _ANSI_ARGS_((Tcl_Interp *interp, + Tcl_Obj *objPtr)); + +/* + * This structure defines a Tcl object type that takes the + * place of a part name during ensemble invocations. When an + * error occurs and the caller tries to print objv[0], it will + * get a string that contains a complete path to the ensemble + * part. + */ +Tcl_ObjType itclEnsInvocType = { + "ensembleInvoc", /* name */ + FreeEnsInvocInternalRep, /* freeIntRepProc */ + DupEnsInvocInternalRep, /* dupIntRepProc */ + UpdateStringOfEnsInvoc, /* updateStringProc */ + SetEnsInvocFromAny /* setFromAnyProc */ +}; + +/* + * Boolean flag indicating whether or not the "ensemble" object + * type has been registered with the Tcl compiler. + */ +static int ensInitialized = 0; + +/* + * Forward declarations for the procedures used in this file. + */ +static void GetEnsembleUsage _ANSI_ARGS_((Ensemble *ensData, + Tcl_Obj *objPtr)); + +static void GetEnsemblePartUsage _ANSI_ARGS_((EnsemblePart *ensPart, + Tcl_Obj *objPtr)); + +static int CreateEnsemble _ANSI_ARGS_((Tcl_Interp *interp, + Ensemble *parentEnsData, char *ensName)); + +static int AddEnsemblePart _ANSI_ARGS_((Tcl_Interp *interp, + Ensemble* ensData, char* partName, char* usageInfo, + Tcl_ObjCmdProc *objProc, ClientData clientData, + Tcl_CmdDeleteProc *deleteProc, EnsemblePart **rVal)); + +static void DeleteEnsemble _ANSI_ARGS_((ClientData clientData)); + +static int FindEnsemble _ANSI_ARGS_((Tcl_Interp *interp, char **nameArgv, + int nameArgc, Ensemble** ensDataPtr)); + +static int CreateEnsemblePart _ANSI_ARGS_((Tcl_Interp *interp, + Ensemble *ensData, char* partName, EnsemblePart **ensPartPtr)); + +static void DeleteEnsemblePart _ANSI_ARGS_((EnsemblePart *ensPart)); + +static int FindEnsemblePart _ANSI_ARGS_((Tcl_Interp *interp, + Ensemble *ensData, char* partName, EnsemblePart **rensPart)); + +static int FindEnsemblePartIndex _ANSI_ARGS_((Ensemble *ensData, + char *partName, int *posPtr)); + +static void ComputeMinChars _ANSI_ARGS_((Ensemble *ensData, int pos)); + +static int HandleEnsemble _ANSI_ARGS_((ClientData clientData, + Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])); + +static EnsembleParser* GetEnsembleParser _ANSI_ARGS_((Tcl_Interp *interp)); + +static void DeleteEnsParser _ANSI_ARGS_((ClientData clientData, + Tcl_Interp* interp)); + + + +/* + *---------------------------------------------------------------------- + * + * Itcl_EnsembleInit -- + * + * Called when any interpreter is created to make sure that + * things are properly set up for ensembles. + * + * Results: + * Returns TCL_OK if successful, and TCL_ERROR if anything goes + * wrong. + * + * Side effects: + * On the first call, the "ensemble" object type is registered + * with the Tcl compiler. If an error is encountered, an error + * is left as the result in the interpreter. + * + *---------------------------------------------------------------------- + */ + /* ARGSUSED */ +int +Itcl_EnsembleInit(interp) + Tcl_Interp *interp; /* interpreter being initialized */ +{ + if (!ensInitialized) { + Tcl_RegisterObjType(&itclEnsInvocType); + ensInitialized = 1; + } + + Tcl_CreateObjCommand(interp, "::itcl::ensemble", + Itcl_EnsembleCmd, (ClientData)NULL, (Tcl_CmdDeleteProc*)NULL); + + return TCL_OK; +} + + +/* + *---------------------------------------------------------------------- + * + * Itcl_CreateEnsemble -- + * + * Creates an ensemble command, or adds a sub-ensemble to an + * existing ensemble command. The ensemble name is a space- + * separated list. The first word in the list is the command + * name for the top-level ensemble. Other names do not have + * commands associated with them; they are merely sub-ensembles + * within the ensemble. So a name like "a::b::foo bar baz" + * represents an ensemble command called "foo" in the namespace + * "a::b" that has a sub-ensemble "bar", that has a sub-ensemble + * "baz". + * + * If the name is a single word, then this procedure creates + * a top-level ensemble and installs an access command for it. + * If a command already exists with that name, it is deleted. + * + * If the name has more than one word, then the leading words + * are treated as a path name for an existing ensemble. The + * last word is treated as the name for a new sub-ensemble. + * If an part already exists with that name, it is an error. + * + * Results: + * Returns TCL_OK if successful, and TCL_ERROR if anything goes + * wrong. + * + * Side effects: + * If an error is encountered, an error is left as the result + * in the interpreter. + * + *---------------------------------------------------------------------- + */ +int +Itcl_CreateEnsemble(interp, ensName) + Tcl_Interp *interp; /* interpreter to be updated */ + char* ensName; /* name of the new ensemble */ +{ + char **nameArgv = NULL; + int nameArgc; + Ensemble *parentEnsData; + Tcl_DString buffer; + + /* + * Split the ensemble name into its path components. + */ + if (Tcl_SplitList(interp, ensName, &nameArgc, &nameArgv) != TCL_OK) { + goto ensCreateFail; + } + if (nameArgc < 1) { + Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), + "invalid ensemble name \"", ensName, "\"", + (char*)NULL); + goto ensCreateFail; + } + + /* + * If there is more than one path component, then follow + * the path down to the last component, to find the containing + * ensemble. + */ + parentEnsData = NULL; + if (nameArgc > 1) { + if (FindEnsemble(interp, nameArgv, nameArgc-1, &parentEnsData) + != TCL_OK) { + goto ensCreateFail; + } + + if (parentEnsData == NULL) { + char *pname = Tcl_Merge(nameArgc-1, nameArgv); + Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), + "invalid ensemble name \"", pname, "\"", + (char*)NULL); + ckfree(pname); + goto ensCreateFail; + } + } + + /* + * Create the ensemble. + */ + if (CreateEnsemble(interp, parentEnsData, nameArgv[nameArgc-1]) + != TCL_OK) { + goto ensCreateFail; + } + + ckfree((char*)nameArgv); + return TCL_OK; + +ensCreateFail: + if (nameArgv) { + ckfree((char*)nameArgv); + } + Tcl_DStringInit(&buffer); + Tcl_DStringAppend(&buffer, "\n (while creating ensemble \"", -1); + Tcl_DStringAppend(&buffer, ensName, -1); + Tcl_DStringAppend(&buffer, "\")", -1); + Tcl_AddObjErrorInfo(interp, Tcl_DStringValue(&buffer), -1); + Tcl_DStringFree(&buffer); + + return TCL_ERROR; +} + + +/* + *---------------------------------------------------------------------- + * + * Itcl_AddEnsemblePart -- + * + * Adds a part to an ensemble which has been created by + * Itcl_CreateEnsemble. Ensembles are addressed by name, as + * described in Itcl_CreateEnsemble. + * + * If the ensemble already has a part with the specified name, + * this procedure returns an error. Otherwise, it adds a new + * part to the ensemble. + * + * Any client data specified is automatically passed to the + * handling procedure whenever the part is invoked. It is + * automatically destroyed by the deleteProc when the part is + * deleted. + * + * Results: + * Returns TCL_OK if successful, and TCL_ERROR if anything goes + * wrong. + * + * Side effects: + * If an error is encountered, an error is left as the result + * in the interpreter. + * + *---------------------------------------------------------------------- + */ +int +Itcl_AddEnsemblePart(interp, ensName, partName, usageInfo, + objProc, clientData, deleteProc) + + Tcl_Interp *interp; /* interpreter to be updated */ + char* ensName; /* ensemble containing this part */ + char* partName; /* name of the new part */ + char* usageInfo; /* usage info for argument list */ + Tcl_ObjCmdProc *objProc; /* handling procedure for part */ + ClientData clientData; /* client data associated with part */ + Tcl_CmdDeleteProc *deleteProc; /* procedure used to destroy client data */ +{ + char **nameArgv = NULL; + int nameArgc; + Ensemble *ensData; + EnsemblePart *ensPart; + Tcl_DString buffer; + + /* + * Parse the ensemble name and look for a containing ensemble. + */ + if (Tcl_SplitList(interp, ensName, &nameArgc, &nameArgv) != TCL_OK) { + goto ensPartFail; + } + if (FindEnsemble(interp, nameArgv, nameArgc, &ensData) != TCL_OK) { + goto ensPartFail; + } + + if (ensData == NULL) { + char *pname = Tcl_Merge(nameArgc, nameArgv); + Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), + "invalid ensemble name \"", pname, "\"", + (char*)NULL); + ckfree(pname); + goto ensPartFail; + } + + /* + * Install the new part into the part list. + */ + if (AddEnsemblePart(interp, ensData, partName, usageInfo, + objProc, clientData, deleteProc, &ensPart) != TCL_OK) { + goto ensPartFail; + } + + ckfree((char*)nameArgv); + return TCL_OK; + +ensPartFail: + if (nameArgv) { + ckfree((char*)nameArgv); + } + Tcl_DStringInit(&buffer); + Tcl_DStringAppend(&buffer, "\n (while adding to ensemble \"", -1); + Tcl_DStringAppend(&buffer, ensName, -1); + Tcl_DStringAppend(&buffer, "\")", -1); + Tcl_AddObjErrorInfo(interp, Tcl_DStringValue(&buffer), -1); + Tcl_DStringFree(&buffer); + + return TCL_ERROR; +} + + +/* + *---------------------------------------------------------------------- + * + * Itcl_GetEnsemblePart -- + * + * Looks for a part within an ensemble, and returns information + * about it. + * + * Results: + * If the ensemble and its part are found, this procedure + * loads information about the part into the "infoPtr" structure + * and returns 1. Otherwise, it returns 0. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ +int +Itcl_GetEnsemblePart(interp, ensName, partName, infoPtr) + Tcl_Interp *interp; /* interpreter to be updated */ + char *ensName; /* ensemble containing the part */ + char *partName; /* name of the desired part */ + Tcl_CmdInfo *infoPtr; /* returns: info associated with part */ +{ + char **nameArgv = NULL; + int nameArgc; + Ensemble *ensData; + EnsemblePart *ensPart; + Command *cmdPtr; + Itcl_InterpState state; + + /* + * Parse the ensemble name and look for a containing ensemble. + * Save the interpreter state before we do this. If we get any + * errors, we don't want them to affect the interpreter. + */ + state = Itcl_SaveInterpState(interp, TCL_OK); + + if (Tcl_SplitList(interp, ensName, &nameArgc, &nameArgv) != TCL_OK) { + goto ensGetFail; + } + if (FindEnsemble(interp, nameArgv, nameArgc, &ensData) != TCL_OK) { + goto ensGetFail; + } + if (ensData == NULL) { + goto ensGetFail; + } + + /* + * Look for a part with the desired name. If found, load + * its data into the "infoPtr" structure. + */ + if (FindEnsemblePart(interp, ensData, partName, &ensPart) + != TCL_OK || ensPart == NULL) { + goto ensGetFail; + } + + cmdPtr = ensPart->cmdPtr; + infoPtr->isNativeObjectProc = (cmdPtr->objProc != TclInvokeStringCommand); + infoPtr->objProc = cmdPtr->objProc; + infoPtr->objClientData = cmdPtr->objClientData; + infoPtr->proc = cmdPtr->proc; + infoPtr->clientData = cmdPtr->clientData; + infoPtr->deleteProc = cmdPtr->deleteProc; + infoPtr->deleteData = cmdPtr->deleteData; + infoPtr->namespacePtr = (Tcl_Namespace*)cmdPtr->nsPtr; + + Itcl_DiscardInterpState(state); + return 1; + +ensGetFail: + Itcl_RestoreInterpState(interp, state); + return 0; +} + + +/* + *---------------------------------------------------------------------- + * + * Itcl_IsEnsemble -- + * + * Determines whether or not an existing command is an ensemble. + * + * Results: + * Returns non-zero if the command is an ensemble, and zero + * otherwise. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ +int +Itcl_IsEnsemble(infoPtr) + Tcl_CmdInfo* infoPtr; /* command info from Tcl_GetCommandInfo() */ +{ + if (infoPtr) { + return (infoPtr->deleteProc == DeleteEnsemble); + } + return 0; +} + + +/* + *---------------------------------------------------------------------- + * + * Itcl_GetEnsembleUsage -- + * + * Returns a summary of all of the parts of an ensemble and + * the meaning of their arguments. Each part is listed on + * a separate line. Having this summary is sometimes useful + * when building error messages for the "@error" handler in + * an ensemble. + * + * Ensembles are accessed by name, as described in + * Itcl_CreateEnsemble. + * + * Results: + * If the ensemble is found, its usage information is appended + * onto the object "objPtr", and this procedure returns + * non-zero. It is the responsibility of the caller to + * initialize and free the object. If anything goes wrong, + * this procedure returns 0. + * + * Side effects: + * Object passed in is modified. + * + *---------------------------------------------------------------------- + */ +int +Itcl_GetEnsembleUsage(interp, ensName, objPtr) + Tcl_Interp *interp; /* interpreter containing the ensemble */ + char *ensName; /* name of the ensemble */ + Tcl_Obj *objPtr; /* returns: summary of usage info */ +{ + char **nameArgv = NULL; + int nameArgc; + Ensemble *ensData; + Itcl_InterpState state; + + /* + * Parse the ensemble name and look for the ensemble. + * Save the interpreter state before we do this. If we get + * any errors, we don't want them to affect the interpreter. + */ + state = Itcl_SaveInterpState(interp, TCL_OK); + + if (Tcl_SplitList(interp, ensName, &nameArgc, &nameArgv) != TCL_OK) { + goto ensUsageFail; + } + if (FindEnsemble(interp, nameArgv, nameArgc, &ensData) != TCL_OK) { + goto ensUsageFail; + } + if (ensData == NULL) { + goto ensUsageFail; + } + + /* + * Add a summary of usage information to the return buffer. + */ + GetEnsembleUsage(ensData, objPtr); + + Itcl_DiscardInterpState(state); + return 1; + +ensUsageFail: + Itcl_RestoreInterpState(interp, state); + return 0; +} + + +/* + *---------------------------------------------------------------------- + * + * Itcl_GetEnsembleUsageForObj -- + * + * Returns a summary of all of the parts of an ensemble and + * the meaning of their arguments. This procedure is just + * like Itcl_GetEnsembleUsage, but it determines the desired + * ensemble from a command line argument. The argument should + * be the first argument on the command line--the ensemble + * command or one of its parts. + * + * Results: + * If the ensemble is found, its usage information is appended + * onto the object "objPtr", and this procedure returns + * non-zero. It is the responsibility of the caller to + * initialize and free the object. If anything goes wrong, + * this procedure returns 0. + * + * Side effects: + * Object passed in is modified. + * + *---------------------------------------------------------------------- + */ +int +Itcl_GetEnsembleUsageForObj(interp, ensObjPtr, objPtr) + Tcl_Interp *interp; /* interpreter containing the ensemble */ + Tcl_Obj *ensObjPtr; /* argument representing ensemble */ + Tcl_Obj *objPtr; /* returns: summary of usage info */ +{ + Ensemble *ensData; + Tcl_Obj *chainObj; + Tcl_Command cmd; + Command *cmdPtr; + + /* + * If the argument is an ensemble part, then follow the chain + * back to the command word for the entire ensemble. + */ + chainObj = ensObjPtr; + while (chainObj && chainObj->typePtr == &itclEnsInvocType) { + chainObj = (Tcl_Obj*)chainObj->internalRep.twoPtrValue.ptr2; + } + + if (chainObj) { + cmd = Tcl_GetCommandFromObj(interp, chainObj); + cmdPtr = (Command*)cmd; + if (cmdPtr->deleteProc == DeleteEnsemble) { + ensData = (Ensemble*)cmdPtr->objClientData; + GetEnsembleUsage(ensData, objPtr); + return 1; + } + } + return 0; +} + + +/* + *---------------------------------------------------------------------- + * + * GetEnsembleUsage -- + * + * + * Returns a summary of all of the parts of an ensemble and + * the meaning of their arguments. Each part is listed on + * a separate line. This procedure is used internally to + * generate usage information for error messages. + * + * Results: + * Appends usage information onto the object in "objPtr". + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ +static void +GetEnsembleUsage(ensData, objPtr) + Ensemble *ensData; /* ensemble data */ + Tcl_Obj *objPtr; /* returns: summary of usage info */ +{ + char *spaces = " "; + int isOpenEnded = 0; + + int i; + EnsemblePart *ensPart; + + for (i=0; i < ensData->numParts; i++) { + ensPart = ensData->parts[i]; + + if (*ensPart->name == '@' && strcmp(ensPart->name,"@error") == 0) { + isOpenEnded = 1; + } + else { + Tcl_AppendToObj(objPtr, spaces, -1); + GetEnsemblePartUsage(ensPart, objPtr); + spaces = "\n "; + } + } + if (isOpenEnded) { + Tcl_AppendToObj(objPtr, + "\n...and others described on the man page", -1); + } +} + + +/* + *---------------------------------------------------------------------- + * + * GetEnsemblePartUsage -- + * + * Determines the usage for a single part within an ensemble, + * and appends a summary onto a dynamic string. The usage + * is a combination of the part name and the argument summary. + * It is the caller's responsibility to initialize and free + * the dynamic string. + * + * Results: + * Returns usage information in the object "objPtr". + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ +static void +GetEnsemblePartUsage(ensPart, objPtr) + EnsemblePart *ensPart; /* ensemble part for usage info */ + Tcl_Obj *objPtr; /* returns: usage information */ +{ + EnsemblePart *part; + Command *cmdPtr; + char *name; + Itcl_List trail; + Itcl_ListElem *elem; + Tcl_DString buffer; + + /* + * Build the trail of ensemble names leading to this part. + */ + Tcl_DStringInit(&buffer); + Itcl_InitList(&trail); + for (part=ensPart; part; part=part->ensemble->parent) { + Itcl_InsertList(&trail, (ClientData)part); + } + + cmdPtr = (Command*)ensPart->ensemble->cmd; + name = Tcl_GetHashKey(cmdPtr->hPtr->tablePtr, cmdPtr->hPtr); + Tcl_DStringAppendElement(&buffer, name); + + for (elem=Itcl_FirstListElem(&trail); elem; elem=Itcl_NextListElem(elem)) { + part = (EnsemblePart*)Itcl_GetListValue(elem); + Tcl_DStringAppendElement(&buffer, part->name); + } + Itcl_DeleteList(&trail); + + /* + * If the part has usage info, use it directly. + */ + if (ensPart->usage && *ensPart->usage != '\0') { + Tcl_DStringAppend(&buffer, " ", 1); + Tcl_DStringAppend(&buffer, ensPart->usage, -1); + } + + /* + * If the part is itself an ensemble, summarize its usage. + */ + else if (ensPart->cmdPtr && + ensPart->cmdPtr->deleteProc == DeleteEnsemble) { + Tcl_DStringAppend(&buffer, " option ?arg arg ...?", 21); + } + + Tcl_AppendToObj(objPtr, Tcl_DStringValue(&buffer), + Tcl_DStringLength(&buffer)); + + Tcl_DStringFree(&buffer); +} + + +/* + *---------------------------------------------------------------------- + * + * CreateEnsemble -- + * + * Creates an ensemble command, or adds a sub-ensemble to an + * existing ensemble command. Works like Itcl_CreateEnsemble, + * except that the ensemble name is a single name, not a path. + * If a parent ensemble is specified, then a new ensemble is + * added to that parent. If a part already exists with the + * same name, it is an error. If a parent ensemble is not + * specified, then a top-level ensemble is created. If a + * command already exists with the same name, it is deleted. + * + * Results: + * Returns TCL_OK if successful, and TCL_ERROR if anything goes + * wrong. + * + * Side effects: + * If an error is encountered, an error is left as the result + * in the interpreter. + * + *---------------------------------------------------------------------- + */ +static int +CreateEnsemble(interp, parentEnsData, ensName) + Tcl_Interp *interp; /* interpreter to be updated */ + Ensemble *parentEnsData; /* parent ensemble or NULL */ + char *ensName; /* name of the new ensemble */ +{ + Ensemble *ensData; + EnsemblePart *ensPart; + Command *cmdPtr; + Tcl_CmdInfo cmdInfo; + + /* + * Create the data associated with the ensemble. + */ + ensData = (Ensemble*)ckalloc(sizeof(Ensemble)); + ensData->interp = interp; + ensData->numParts = 0; + ensData->maxParts = 10; + ensData->parts = (EnsemblePart**)ckalloc( + (unsigned)(ensData->maxParts*sizeof(EnsemblePart*)) + ); + ensData->cmd = NULL; + ensData->parent = NULL; + + /* + * If there is no parent data, then this is a top-level + * ensemble. Create the ensemble by installing its access + * command. + * + * BE CAREFUL: Set the string-based proc to the wrapper + * procedure TclInvokeObjectCommand. Otherwise, the + * ensemble command may fail. For example, it will fail + * when invoked as a hidden command. + */ + if (parentEnsData == NULL) { + ensData->cmd = Tcl_CreateObjCommand(interp, ensName, + HandleEnsemble, (ClientData)ensData, DeleteEnsemble); + + if (Tcl_GetCommandInfo(interp, ensName, &cmdInfo)) { + cmdInfo.proc = TclInvokeObjectCommand; + Tcl_SetCommandInfo(interp, ensName, &cmdInfo); + } + return TCL_OK; + } + + /* + * Otherwise, this ensemble is contained within another parent. + * Install the new ensemble as a part within its parent. + */ + if (CreateEnsemblePart(interp, parentEnsData, ensName, &ensPart) + != TCL_OK) { + DeleteEnsemble((ClientData)ensData); + return TCL_ERROR; + } + + ensData->cmd = parentEnsData->cmd; + ensData->parent = ensPart; + + cmdPtr = (Command*)ckalloc(sizeof(Command)); + cmdPtr->hPtr = NULL; + cmdPtr->nsPtr = ((Command*)ensData->cmd)->nsPtr; + cmdPtr->refCount = 0; + cmdPtr->cmdEpoch = 0; + cmdPtr->compileProc = NULL; + cmdPtr->objProc = HandleEnsemble; + cmdPtr->objClientData = (ClientData)ensData; + cmdPtr->proc = NULL; + cmdPtr->clientData = NULL; + cmdPtr->deleteProc = DeleteEnsemble; + cmdPtr->deleteData = cmdPtr->objClientData; + cmdPtr->deleted = 0; + cmdPtr->importRefPtr = NULL; + + ensPart->cmdPtr = cmdPtr; + + return TCL_OK; +} + + +/* + *---------------------------------------------------------------------- + * + * AddEnsemblePart -- + * + * Adds a part to an existing ensemble. Works like + * Itcl_AddEnsemblePart, but the part name is a single word, + * not a path. + * + * If the ensemble already has a part with the specified name, + * this procedure returns an error. Otherwise, it adds a new + * part to the ensemble. + * + * Any client data specified is automatically passed to the + * handling procedure whenever the part is invoked. It is + * automatically destroyed by the deleteProc when the part is + * deleted. + * + * Results: + * Returns TCL_OK if successful, along with a pointer to the + * new part. Returns TCL_ERROR if anything goes wrong. + * + * Side effects: + * If an error is encountered, an error is left as the result + * in the interpreter. + * + *---------------------------------------------------------------------- + */ +static int +AddEnsemblePart(interp, ensData, partName, usageInfo, + objProc, clientData, deleteProc, rVal) + + Tcl_Interp *interp; /* interpreter to be updated */ + Ensemble* ensData; /* ensemble that will contain this part */ + char* partName; /* name of the new part */ + char* usageInfo; /* usage info for argument list */ + Tcl_ObjCmdProc *objProc; /* handling procedure for part */ + ClientData clientData; /* client data associated with part */ + Tcl_CmdDeleteProc *deleteProc; /* procedure used to destroy client data */ + EnsemblePart **rVal; /* returns: new ensemble part */ +{ + EnsemblePart *ensPart; + Command *cmdPtr; + + /* + * Install the new part into the part list. + */ + if (CreateEnsemblePart(interp, ensData, partName, &ensPart) != TCL_OK) { + return TCL_ERROR; + } + + if (usageInfo) { + ensPart->usage = ckalloc((unsigned)(strlen(usageInfo)+1)); + strcpy(ensPart->usage, usageInfo); + } + + cmdPtr = (Command*)ckalloc(sizeof(Command)); + cmdPtr->hPtr = NULL; + cmdPtr->nsPtr = ((Command*)ensData->cmd)->nsPtr; + cmdPtr->refCount = 0; + cmdPtr->cmdEpoch = 0; + cmdPtr->compileProc = NULL; + cmdPtr->objProc = objProc; + cmdPtr->objClientData = (ClientData)clientData; + cmdPtr->proc = NULL; + cmdPtr->clientData = NULL; + cmdPtr->deleteProc = deleteProc; + cmdPtr->deleteData = (ClientData)clientData; + cmdPtr->deleted = 0; + cmdPtr->importRefPtr = NULL; + + ensPart->cmdPtr = cmdPtr; + *rVal = ensPart; + + return TCL_OK; +} + + +/* + *---------------------------------------------------------------------- + * + * DeleteEnsemble -- + * + * Invoked when the command associated with an ensemble is + * destroyed, to delete the ensemble. Destroys all parts + * included in the ensemble, and frees all memory associated + * with it. + * + * Results: + * None. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ +static void +DeleteEnsemble(clientData) + ClientData clientData; /* ensemble data */ +{ + Ensemble* ensData = (Ensemble*)clientData; + + /* + * BE CAREFUL: Each ensemble part removes itself from the list. + * So keep deleting the first part until all parts are gone. + */ + while (ensData->numParts > 0) { + DeleteEnsemblePart(ensData->parts[0]); + } + ckfree((char*)ensData->parts); + ckfree((char*)ensData); +} + + +/* + *---------------------------------------------------------------------- + * + * FindEnsemble -- + * + * Searches for an ensemble command and follows a path to + * sub-ensembles. + * + * Results: + * Returns TCL_OK if the ensemble was found, along with a + * pointer to the ensemble data in "ensDataPtr". Returns + * TCL_ERROR if anything goes wrong. + * + * Side effects: + * If anything goes wrong, this procedure returns an error + * message as the result in the interpreter. + * + *---------------------------------------------------------------------- + */ +static int +FindEnsemble(interp, nameArgv, nameArgc, ensDataPtr) + Tcl_Interp *interp; /* interpreter containing the ensemble */ + char **nameArgv; /* path of names leading to ensemble */ + int nameArgc; /* number of strings in nameArgv */ + Ensemble** ensDataPtr; /* returns: ensemble data */ +{ + int i; + Command* cmdPtr; + Ensemble *ensData; + EnsemblePart *ensPart; + + *ensDataPtr = NULL; /* assume that no data will be found */ + + /* + * If there are no names in the path, then return an error. + */ + if (nameArgc < 1) { + Tcl_AppendToObj(Tcl_GetObjResult(interp), + "invalid ensemble name \"\"", -1); + return TCL_ERROR; + } + + /* + * Use the first name to find the command for the top-level + * ensemble. + */ + cmdPtr = (Command*) Tcl_FindCommand(interp, nameArgv[0], + (Tcl_Namespace*)NULL, TCL_LEAVE_ERR_MSG); + + if (cmdPtr == NULL || cmdPtr->deleteProc != DeleteEnsemble) { + Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), + "command \"", nameArgv[0], "\" is not an ensemble", + (char*)NULL); + return TCL_ERROR; + } + ensData = (Ensemble*)cmdPtr->objClientData; + + /* + * Follow the trail of sub-ensemble names. + */ + for (i=1; i < nameArgc; i++) { + if (FindEnsemblePart(interp, ensData, nameArgv[i], &ensPart) + != TCL_OK) { + return TCL_ERROR; + } + if (ensPart == NULL) { + char *pname = Tcl_Merge(i, nameArgv); + Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), + "invalid ensemble name \"", pname, "\"", + (char*)NULL); + ckfree(pname); + return TCL_ERROR; + } + + cmdPtr = ensPart->cmdPtr; + if (cmdPtr == NULL || cmdPtr->deleteProc != DeleteEnsemble) { + Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), + "part \"", nameArgv[i], "\" is not an ensemble", + (char*)NULL); + return TCL_ERROR; + } + ensData = (Ensemble*)cmdPtr->objClientData; + } + *ensDataPtr = ensData; + + return TCL_OK; +} + + +/* + *---------------------------------------------------------------------- + * + * CreateEnsemblePart -- + * + * Creates a new part within an ensemble. + * + * Results: + * If successful, this procedure returns TCL_OK, along with a + * pointer to the new part in "ensPartPtr". If a part with the + * same name already exists, this procedure returns TCL_ERROR. + * + * Side effects: + * If anything goes wrong, this procedure returns an error + * message as the result in the interpreter. + * + *---------------------------------------------------------------------- + */ +static int +CreateEnsemblePart(interp, ensData, partName, ensPartPtr) + Tcl_Interp *interp; /* interpreter containing the ensemble */ + Ensemble *ensData; /* ensemble being modified */ + char* partName; /* name of the new part */ + EnsemblePart **ensPartPtr; /* returns: new ensemble part */ +{ + int i, pos, size; + EnsemblePart** partList; + EnsemblePart* part; + + /* + * If a matching entry was found, then return an error. + */ + if (FindEnsemblePartIndex(ensData, partName, &pos)) { + Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), + "part \"", partName, "\" already exists in ensemble", + (char*)NULL); + return TCL_ERROR; + } + + /* + * Otherwise, make room for a new entry. Keep the parts in + * lexicographical order, so we can search them quickly + * later. + */ + if (ensData->numParts >= ensData->maxParts) { + size = ensData->maxParts*sizeof(EnsemblePart*); + partList = (EnsemblePart**)ckalloc((unsigned)2*size); + memcpy((VOID*)partList, (VOID*)ensData->parts, (size_t)size); + ckfree((char*)ensData->parts); + + ensData->parts = partList; + ensData->maxParts *= 2; + } + + for (i=ensData->numParts; i > pos; i--) { + ensData->parts[i] = ensData->parts[i-1]; + } + ensData->numParts++; + + part = (EnsemblePart*)ckalloc(sizeof(EnsemblePart)); + part->name = (char*)ckalloc((unsigned)(strlen(partName)+1)); + strcpy(part->name, partName); + part->cmdPtr = NULL; + part->usage = NULL; + part->ensemble = ensData; + + ensData->parts[pos] = part; + + /* + * Compare the new part against the one on either side of + * it. Determine how many letters are needed in each part + * to guarantee that an abbreviated form is unique. Update + * the parts on either side as well, since they are influenced + * by the new part. + */ + ComputeMinChars(ensData, pos); + ComputeMinChars(ensData, pos-1); + ComputeMinChars(ensData, pos+1); + + *ensPartPtr = part; + return TCL_OK; +} + + +/* + *---------------------------------------------------------------------- + * + * DeleteEnsemblePart -- + * + * Deletes a single part from an ensemble. The part must have + * been created previously by CreateEnsemblePart. + * + * If the part has a delete proc, then it is called to free the + * associated client data. + * + * Results: + * None. + * + * Side effects: + * Delete proc is called. + * + *---------------------------------------------------------------------- + */ +static void +DeleteEnsemblePart(ensPart) + EnsemblePart *ensPart; /* part being destroyed */ +{ + int i, pos; + Command *cmdPtr; + Ensemble *ensData; + cmdPtr = ensPart->cmdPtr; + + /* + * If this part has a delete proc, then call it to free + * up the client data. + */ + if (cmdPtr->deleteData && cmdPtr->deleteProc) { + (*cmdPtr->deleteProc)(cmdPtr->deleteData); + } + ckfree((char*)cmdPtr); + + /* + * Find this part within its ensemble, and remove it from + * the list of parts. + */ + if (FindEnsemblePartIndex(ensPart->ensemble, ensPart->name, &pos)) { + ensData = ensPart->ensemble; + for (i=pos; i < ensData->numParts-1; i++) { + ensData->parts[i] = ensData->parts[i+1]; + } + ensData->numParts--; + } + + /* + * Free the memory associated with the part. + */ + if (ensPart->usage) { + ckfree(ensPart->usage); + } + ckfree(ensPart->name); + ckfree((char*)ensPart); +} + + +/* + *---------------------------------------------------------------------- + * + * FindEnsemblePart -- + * + * Searches for a part name within an ensemble. Recognizes + * unique abbreviations for part names. + * + * Results: + * If the part name is not a unique abbreviation, this procedure + * returns TCL_ERROR. Otherwise, it returns TCL_OK. If the + * part can be found, "rensPart" returns a pointer to the part. + * Otherwise, it returns NULL. + * + * Side effects: + * If anything goes wrong, this procedure returns an error + * message as the result in the interpreter. + * + *---------------------------------------------------------------------- + */ +static int +FindEnsemblePart(interp, ensData, partName, rensPart) + Tcl_Interp *interp; /* interpreter containing the ensemble */ + Ensemble *ensData; /* ensemble being searched */ + char* partName; /* name of the desired part */ + EnsemblePart **rensPart; /* returns: pointer to the desired part */ +{ + int pos = 0; + int first, last, nlen; + int i, cmp; + + *rensPart = NULL; + + /* + * Search for the desired part name. + * All parts are in lexicographical order, so use a + * binary search to find the part quickly. Match only + * as many characters as are included in the specified + * part name. + */ + first = 0; + last = ensData->numParts-1; + nlen = strlen(partName); + + while (last >= first) { + pos = (first+last)/2; + if (*partName == *ensData->parts[pos]->name) { + cmp = strncmp(partName, ensData->parts[pos]->name, nlen); + if (cmp == 0) { + break; /* found it! */ + } + } + else if (*partName < *ensData->parts[pos]->name) { + cmp = -1; + } + else { + cmp = 1; + } + + if (cmp > 0) { + first = pos+1; + } else { + last = pos-1; + } + } + + /* + * If a matching entry could not be found, then quit. + */ + if (last < first) { + return TCL_OK; + } + + /* + * If a matching entry was found, there may be some ambiguity + * if the user did not specify enough characters. Find the + * top-most match in the list, and see if the part name has + * enough characters. If there are two parts like "foo" + * and "food", this allows us to match "foo" exactly. + */ + if (nlen < ensData->parts[pos]->minChars) { + while (pos > 0) { + pos--; + if (strncmp(partName, ensData->parts[pos]->name, nlen) != 0) { + pos++; + break; + } + } + } + if (nlen < ensData->parts[pos]->minChars) { + Tcl_Obj *resultPtr = Tcl_NewStringObj((char*)NULL, 0); + + Tcl_AppendStringsToObj(resultPtr, + "ambiguous option \"", partName, "\": should be one of...", + (char*)NULL); + + for (i=pos; i < ensData->numParts; i++) { + if (strncmp(partName, ensData->parts[i]->name, nlen) != 0) { + break; + } + Tcl_AppendToObj(resultPtr, "\n ", 3); + GetEnsemblePartUsage(ensData->parts[i], resultPtr); + } + Tcl_SetObjResult(interp, resultPtr); + return TCL_ERROR; + } + + /* + * Found a match. Return the desired part. + */ + *rensPart = ensData->parts[pos]; + return TCL_OK; +} + + +/* + *---------------------------------------------------------------------- + * + * FindEnsemblePartIndex -- + * + * Searches for a part name within an ensemble. The part name + * must be an exact match for an existing part name in the + * ensemble. This procedure is useful for managing (i.e., + * creating and deleting) parts in an ensemble. + * + * Results: + * If an exact match is found, this procedure returns + * non-zero, along with the index of the part in posPtr. + * Otherwise, it returns zero, along with an index in posPtr + * indicating where the part should be. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ +static int +FindEnsemblePartIndex(ensData, partName, posPtr) + Ensemble *ensData; /* ensemble being searched */ + char *partName; /* name of desired part */ + int *posPtr; /* returns: index for part */ +{ + int pos = 0; + int first, last; + int cmp; + + /* + * Search for the desired part name. + * All parts are in lexicographical order, so use a + * binary search to find the part quickly. + */ + first = 0; + last = ensData->numParts-1; + + while (last >= first) { + pos = (first+last)/2; + if (*partName == *ensData->parts[pos]->name) { + cmp = strcmp(partName, ensData->parts[pos]->name); + if (cmp == 0) { + break; /* found it! */ + } + } + else if (*partName < *ensData->parts[pos]->name) { + cmp = -1; + } + else { + cmp = 1; + } + + if (cmp > 0) { + first = pos+1; + } else { + last = pos-1; + } + } + + if (last >= first) { + *posPtr = pos; + return 1; + } + *posPtr = first; + return 0; +} + + +/* + *---------------------------------------------------------------------- + * + * ComputeMinChars -- + * + * Compares part names on an ensemble's part list and + * determines the minimum number of characters needed for a + * unique abbreviation. The parts on either side of a + * particular part index are compared. As long as there is + * a part on one side or the other, this procedure updates + * the parts to have the proper minimum abbreviations. + * + * Results: + * None. + * + * Side effects: + * Updates three parts within the ensemble to remember + * the minimum abbreviations. + * + *---------------------------------------------------------------------- + */ +static void +ComputeMinChars(ensData, pos) + Ensemble *ensData; /* ensemble being modified */ + int pos; /* index of part being updated */ +{ + int min, max; + char *p, *q; + + /* + * If the position is invalid, do nothing. + */ + if (pos < 0 || pos >= ensData->numParts) { + return; + } + + /* + * Start by assuming that only the first letter is required + * to uniquely identify this part. Then compare the name + * against each neighboring part to determine the real minimum. + */ + ensData->parts[pos]->minChars = 1; + + if (pos-1 >= 0) { + p = ensData->parts[pos]->name; + q = ensData->parts[pos-1]->name; + for (min=1; *p == *q && *p != '\0' && *q != '\0'; min++) { + p++; + q++; + } + if (min > ensData->parts[pos]->minChars) { + ensData->parts[pos]->minChars = min; + } + } + + if (pos+1 < ensData->numParts) { + p = ensData->parts[pos]->name; + q = ensData->parts[pos+1]->name; + for (min=1; *p == *q && *p != '\0' && *q != '\0'; min++) { + p++; + q++; + } + if (min > ensData->parts[pos]->minChars) { + ensData->parts[pos]->minChars = min; + } + } + + max = strlen(ensData->parts[pos]->name); + if (ensData->parts[pos]->minChars > max) { + ensData->parts[pos]->minChars = max; + } +} + + +/* + *---------------------------------------------------------------------- + * + * HandleEnsemble -- + * + * Invoked by Tcl whenever the user issues an ensemble-style + * command. Handles commands of the form: + * + * ? ...? + * + * Looks for the within the ensemble, and if it + * exists, the procedure transfers control to it. + * + * Results: + * Returns TCL_OK if successful, and TCL_ERROR if anything + * goes wrong. + * + * Side effects: + * If anything goes wrong, this procedure returns an error + * message as the result in the interpreter. + * + *---------------------------------------------------------------------- + */ +static int +HandleEnsemble(clientData, interp, objc, objv) + ClientData clientData; /* ensemble data */ + Tcl_Interp *interp; /* current interpreter */ + int objc; /* number of arguments */ + Tcl_Obj *CONST objv[]; /* argument objects */ +{ + Ensemble *ensData = (Ensemble*)clientData; + + int i, result; + Command *cmdPtr; + EnsemblePart *ensPart; + char *partName; + int partNameLen; + Tcl_Obj *cmdlinePtr, *chainObj; + int cmdlinec; + Tcl_Obj **cmdlinev; + + /* + * If a part name is not specified, return an error that + * summarizes the usage for this ensemble. + */ + if (objc < 2) { + Tcl_Obj *resultPtr = Tcl_NewStringObj( + "wrong # args: should be one of...\n", -1); + + GetEnsembleUsage(ensData, resultPtr); + Tcl_SetObjResult(interp, resultPtr); + return TCL_ERROR; + } + + /* + * Lookup the desired part. If an ambiguous abbrevition is + * found, return an error immediately. + */ + partName = Tcl_GetStringFromObj(objv[1], &partNameLen); + if (FindEnsemblePart(interp, ensData, partName, &ensPart) != TCL_OK) { + return TCL_ERROR; + } + + /* + * If the part was not found, then look for an "@error" part + * to handle the error. + */ + if (ensPart == NULL) { + if (FindEnsemblePart(interp, ensData, "@error", &ensPart) != TCL_OK) { + return TCL_ERROR; + } + if (ensPart != NULL) { + cmdPtr = (Command*)ensPart->cmdPtr; + result = (*cmdPtr->objProc)(cmdPtr->objClientData, + interp, objc, objv); + return result; + } + } + if (ensPart == NULL) { + return Itcl_EnsembleErrorCmd((ClientData)ensData, + interp, objc-1, objv+1); + } + + /* + * Pass control to the part, and return the result. + */ + chainObj = Tcl_NewObj(); + chainObj->bytes = NULL; + chainObj->typePtr = &itclEnsInvocType; + chainObj->internalRep.twoPtrValue.ptr1 = (VOID *) ensPart; + Tcl_IncrRefCount(objv[1]); + chainObj->internalRep.twoPtrValue.ptr2 = (VOID *) objv[0]; + Tcl_IncrRefCount(objv[0]); + + cmdlinePtr = Tcl_NewListObj(0, (Tcl_Obj**)NULL); + Tcl_ListObjAppendElement((Tcl_Interp*)NULL, cmdlinePtr, chainObj); + for (i=2; i < objc; i++) { + Tcl_ListObjAppendElement((Tcl_Interp*)NULL, cmdlinePtr, objv[i]); + } + Tcl_IncrRefCount(cmdlinePtr); + + result = Tcl_ListObjGetElements((Tcl_Interp*)NULL, cmdlinePtr, + &cmdlinec, &cmdlinev); + + if (result == TCL_OK) { + cmdPtr = (Command*)ensPart->cmdPtr; + result = (*cmdPtr->objProc)(cmdPtr->objClientData, interp, + cmdlinec, cmdlinev); + } + Tcl_DecrRefCount(cmdlinePtr); + + return result; +} + + +/* + *---------------------------------------------------------------------- + * + * Itcl_EnsembleCmd -- + * + * Invoked by Tcl whenever the user issues the "ensemble" + * command to manipulate an ensemble. Handles the following + * syntax: + * + * ensemble ? ...? + * ensemble { + * part + * ensemble { + * ... + * } + * } + * + * Finds or creates the ensemble , and then executes + * the commands to add parts. + * + * Results: + * Returns TCL_OK if successful, and TCL_ERROR if anything + * goes wrong. + * + * Side effects: + * If anything goes wrong, this procedure returns an error + * message as the result in the interpreter. + * + *---------------------------------------------------------------------- + */ +int +Itcl_EnsembleCmd(clientData, interp, objc, objv) + ClientData clientData; /* ensemble data */ + Tcl_Interp *interp; /* current interpreter */ + int objc; /* number of arguments */ + Tcl_Obj *CONST objv[]; /* argument objects */ +{ + int status; + char *ensName; + EnsembleParser *ensInfo; + Ensemble *ensData, *savedEnsData; + EnsemblePart *ensPart; + Tcl_Command cmd; + Command *cmdPtr; + Tcl_Obj *objPtr; + + /* + * Make sure that an ensemble name was specified. + */ + if (objc < 2) { + Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), + "wrong # args: should be \"", + Tcl_GetStringFromObj(objv[0], (int*)NULL), + " name ?command arg arg...?\"", + (char*)NULL); + return TCL_ERROR; + } + + /* + * If this is the "ensemble" command in the main interpreter, + * then the client data will be null. Otherwise, it is + * the "ensemble" command in the ensemble body parser, and + * the client data indicates which ensemble we are modifying. + */ + if (clientData) { + ensInfo = (EnsembleParser*)clientData; + } else { + ensInfo = GetEnsembleParser(interp); + } + ensData = ensInfo->ensData; + + /* + * Find or create the desired ensemble. If an ensemble is + * being built, then this "ensemble" command is enclosed in + * another "ensemble" command. Use the current ensemble as + * the parent, and find or create an ensemble part within it. + */ + ensName = Tcl_GetStringFromObj(objv[1], (int*)NULL); + + if (ensData) { + if (FindEnsemblePart(interp, ensData, ensName, &ensPart) != TCL_OK) { + ensPart = NULL; + } + if (ensPart == NULL) { + if (CreateEnsemble(interp, ensData, ensName) != TCL_OK) { + return TCL_ERROR; + } + if (FindEnsemblePart(interp, ensData, ensName, &ensPart) + != TCL_OK) { + panic("Itcl_EnsembleCmd: can't create ensemble"); + } + } + + cmdPtr = (Command*)ensPart->cmdPtr; + if (cmdPtr->deleteProc != DeleteEnsemble) { + Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), + "part \"", Tcl_GetStringFromObj(objv[1], (int*)NULL), + "\" is not an ensemble", + (char*)NULL); + return TCL_ERROR; + } + ensData = (Ensemble*)cmdPtr->objClientData; + } + + /* + * Otherwise, the desired ensemble is a top-level ensemble. + * Find or create the access command for the ensemble, and + * then get its data. + */ + else { + cmd = Tcl_FindCommand(interp, ensName, (Tcl_Namespace*)NULL, 0); + if (cmd == NULL) { + if (CreateEnsemble(interp, (Ensemble*)NULL, ensName) + != TCL_OK) { + return TCL_ERROR; + } + cmd = Tcl_FindCommand(interp, ensName, (Tcl_Namespace*)NULL, 0); + } + cmdPtr = (Command*)cmd; + + if (cmdPtr == NULL || cmdPtr->deleteProc != DeleteEnsemble) { + Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), + "command \"", Tcl_GetStringFromObj(objv[1], (int*)NULL), + "\" is not an ensemble", + (char*)NULL); + return TCL_ERROR; + } + ensData = (Ensemble*)cmdPtr->objClientData; + } + + /* + * At this point, we have the data for the ensemble that is + * being manipulated. Plug this into the parser, and then + * interpret the rest of the arguments in the ensemble parser. + */ + status = TCL_OK; + savedEnsData = ensInfo->ensData; + ensInfo->ensData = ensData; + + if (objc == 3) { + /* CYGNUS LOCAL - fix for Tcl8.1 */ +#if TCL_MAJOR_VERSION == 8 && TCL_MINOR_VERSION == 0 + status = Tcl_EvalObj(ensInfo->parser, objv[2]); +#else + status = Tcl_EvalObj(ensInfo->parser, objv[2], 0); +#endif + } + else if (objc > 3) { + objPtr = Tcl_NewListObj(objc-2, objv+2); +#if TCL_MAJOR_VERSION == 8 && TCL_MINOR_VERSION == 0 + status = Tcl_EvalObj(ensInfo->parser, objPtr); +#else + Tcl_IncrRefCount(objPtr); + status = Tcl_EvalObj(ensInfo->parser, objPtr, 0); +#endif + /* END CYGNUS LOCAL */ + Tcl_DecrRefCount(objPtr); /* we're done with the object */ + } + + /* + * Copy the result from the parser interpreter to the + * master interpreter. If an error was encountered, + * copy the error info first, and then set the result. + * Otherwise, the offending command is reported twice. + */ + if (status == TCL_ERROR) { + char *errInfo = Tcl_GetVar2(ensInfo->parser, "::errorInfo", + (char*)NULL, TCL_GLOBAL_ONLY); + + if (errInfo) { + Tcl_AddObjErrorInfo(interp, errInfo, -1); + } + + if (objc == 3) { + char msg[128]; + sprintf(msg, "\n (\"ensemble\" body line %d)", + ensInfo->parser->errorLine); + Tcl_AddObjErrorInfo(interp, msg, -1); + } + } + Tcl_SetObjResult(interp, Tcl_GetObjResult(ensInfo->parser)); + + ensInfo->ensData = savedEnsData; + return status; +} + + +/* + *---------------------------------------------------------------------- + * + * GetEnsembleParser -- + * + * Returns the slave interpreter that acts as a parser for + * the body of an "ensemble" definition. The first time that + * this is called for an interpreter, the parser is created + * and registered as associated data. After that, it is + * simply returned. + * + * Results: + * Returns a pointer to the ensemble parser data structure. + * + * Side effects: + * On the first call, the ensemble parser is created and + * registered as "itcl_ensembleParser" with the interpreter. + * + *---------------------------------------------------------------------- + */ +static EnsembleParser* +GetEnsembleParser(interp) + Tcl_Interp *interp; /* interpreter handling the ensemble */ +{ + Namespace *nsPtr; + Tcl_Namespace *childNs; + EnsembleParser *ensInfo; + Tcl_HashEntry *hPtr; + Tcl_HashSearch search; + Tcl_Command cmd; + + /* + * Look for an existing ensemble parser. If it is found, + * return it immediately. + */ + ensInfo = (EnsembleParser*) Tcl_GetAssocData(interp, + "itcl_ensembleParser", NULL); + + if (ensInfo) { + return ensInfo; + } + + /* + * Create a slave interpreter that can be used to parse + * the body of an ensemble definition. + */ + ensInfo = (EnsembleParser*)ckalloc(sizeof(EnsembleParser)); + ensInfo->master = interp; + ensInfo->parser = Tcl_CreateInterp(); + ensInfo->ensData = NULL; + + /* + * Remove all namespaces and all normal commands from the + * parser interpreter. + */ + nsPtr = (Namespace*)Tcl_GetGlobalNamespace(ensInfo->parser); + + for (hPtr = Tcl_FirstHashEntry(&nsPtr->childTable, &search); + hPtr != NULL; + hPtr = Tcl_FirstHashEntry(&nsPtr->childTable, &search)) { + + childNs = (Tcl_Namespace*)Tcl_GetHashValue(hPtr); + Tcl_DeleteNamespace(childNs); + } + + for (hPtr = Tcl_FirstHashEntry(&nsPtr->cmdTable, &search); + hPtr != NULL; + hPtr = Tcl_FirstHashEntry(&nsPtr->cmdTable, &search)) { + + cmd = (Tcl_Command)Tcl_GetHashValue(hPtr); + Tcl_DeleteCommandFromToken(ensInfo->parser, cmd); + } + + /* + * Add the allowed commands to the parser interpreter: + * part, delete, ensemble + */ + Tcl_CreateObjCommand(ensInfo->parser, "part", Itcl_EnsPartCmd, + (ClientData)ensInfo, (Tcl_CmdDeleteProc*)NULL); + + Tcl_CreateObjCommand(ensInfo->parser, "option", Itcl_EnsPartCmd, + (ClientData)ensInfo, (Tcl_CmdDeleteProc*)NULL); + + Tcl_CreateObjCommand(ensInfo->parser, "ensemble", Itcl_EnsembleCmd, + (ClientData)ensInfo, (Tcl_CmdDeleteProc*)NULL); + + /* + * Install the parser data, so we'll have it the next time + * we call this procedure. + */ + (void) Tcl_SetAssocData(interp, "itcl_ensembleParser", + DeleteEnsParser, (ClientData)ensInfo); + + return ensInfo; +} + + +/* + *---------------------------------------------------------------------- + * + * DeleteEnsParser -- + * + * Called when an interpreter is destroyed to clean up the + * ensemble parser within it. Destroys the slave interpreter + * and frees up the data associated with it. + * + * Results: + * None. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + /* ARGSUSED */ +static void +DeleteEnsParser(clientData, interp) + ClientData clientData; /* client data for ensemble-related commands */ + Tcl_Interp *interp; /* interpreter containing the data */ +{ + EnsembleParser* ensInfo = (EnsembleParser*)clientData; + Tcl_DeleteInterp(ensInfo->parser); + ckfree((char*)ensInfo); +} + + +/* + *---------------------------------------------------------------------- + * + * Itcl_EnsPartCmd -- + * + * Invoked by Tcl whenever the user issues the "part" command + * to manipulate an ensemble. This command can only be used + * inside the "ensemble" command, which handles ensembles. + * Handles the following syntax: + * + * ensemble { + * part + * } + * + * Adds a new part called to the ensemble. If a + * part already exists with that name, it is an error. The + * new part is handled just like an ordinary Tcl proc, with + * a list of and a of code to execute. + * + * Results: + * Returns TCL_OK if successful, and TCL_ERROR if anything + * goes wrong. + * + * Side effects: + * If anything goes wrong, this procedure returns an error + * message as the result in the interpreter. + * + *---------------------------------------------------------------------- + */ +int +Itcl_EnsPartCmd(clientData, interp, objc, objv) + ClientData clientData; /* ensemble data */ + Tcl_Interp *interp; /* current interpreter */ + int objc; /* number of arguments */ + Tcl_Obj *CONST objv[]; /* argument objects */ +{ + EnsembleParser *ensInfo = (EnsembleParser*)clientData; + Ensemble *ensData = (Ensemble*)ensInfo->ensData; + + int status, varArgs, space; + char *partName, *usage; + Proc *procPtr; + Command *cmdPtr; + CompiledLocal *localPtr; + EnsemblePart *ensPart; + Tcl_DString buffer; + + if (objc != 4) { + Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), + "wrong # args: should be \"", + Tcl_GetStringFromObj(objv[0], (int*)NULL), + " name args body\"", + (char*)NULL); + return TCL_ERROR; + } + + /* + * Create a Tcl-style proc definition using the specified args + * and body. This is not a proc in the usual sense. It belongs + * to the namespace that contains the ensemble, but it is + * accessed through the ensemble, not through a Tcl command. + */ + partName = Tcl_GetStringFromObj(objv[1], (int*)NULL); + cmdPtr = (Command*)ensData->cmd; + + if (TclCreateProc(interp, cmdPtr->nsPtr, partName, objv[2], objv[3], + &procPtr) != TCL_OK) { + return TCL_ERROR; + } + + /* + * Deduce the usage information from the argument list. + * We'll register this when we create the part, in a moment. + */ + Tcl_DStringInit(&buffer); + varArgs = 0; + space = 0; + + for (localPtr=procPtr->firstLocalPtr; + localPtr != NULL; + localPtr=localPtr->nextPtr) { + + if (TclIsVarArgument(localPtr)) { + varArgs = 0; + if (strcmp(localPtr->name, "args") == 0) { + varArgs = 1; + } + else if (localPtr->defValuePtr) { + if (space) { + Tcl_DStringAppend(&buffer, " ", 1); + } + Tcl_DStringAppend(&buffer, "?", 1); + Tcl_DStringAppend(&buffer, localPtr->name, -1); + Tcl_DStringAppend(&buffer, "?", 1); + space = 1; + } + else { + if (space) { + Tcl_DStringAppend(&buffer, " ", 1); + } + Tcl_DStringAppend(&buffer, localPtr->name, -1); + space = 1; + } + } + } + if (varArgs) { + if (space) { + Tcl_DStringAppend(&buffer, " ", 1); + } + Tcl_DStringAppend(&buffer, "?arg arg ...?", 13); + } + + usage = Tcl_DStringValue(&buffer); + + /* + * Create a new part within the ensemble. If successful, + * plug the command token into the proc; we'll need it later + * if we try to compile the Tcl code for the part. If + * anything goes wrong, clean up before bailing out. + */ + status = AddEnsemblePart(interp, ensData, partName, usage, + TclObjInterpProc, (ClientData)procPtr, TclProcDeleteProc, + &ensPart); + + if (status == TCL_OK) { + procPtr->cmdPtr = ensPart->cmdPtr; + } else { + TclProcDeleteProc((ClientData)procPtr); + } + Tcl_DStringFree(&buffer); + + return status; +} + + +/* + *---------------------------------------------------------------------- + * + * Itcl_EnsembleErrorCmd -- + * + * Invoked when the user tries to access an unknown part for + * an ensemble. Acts as the default handler for the "@error" + * part. Generates an error message like: + * + * bad option "foo": should be one of... + * info args procname + * info body procname + * info cmdcount + * ... + * + * Results: + * Always returns TCL_OK. + * + * Side effects: + * Returns the error message as the result in the interpreter. + * + *---------------------------------------------------------------------- + */ + /* ARGSUSED */ +int +Itcl_EnsembleErrorCmd(clientData, interp, objc, objv) + ClientData clientData; /* ensemble info */ + Tcl_Interp *interp; /* current interpreter */ + int objc; /* number of arguments */ + Tcl_Obj *CONST objv[]; /* argument objects */ +{ + Ensemble *ensData = (Ensemble*)clientData; + + char *cmdName; + Tcl_Obj *objPtr; + + cmdName = Tcl_GetStringFromObj(objv[0], (int*)NULL); + + objPtr = Tcl_NewStringObj((char*)NULL, 0); + Tcl_AppendStringsToObj(objPtr, + "bad option \"", cmdName, "\": should be one of...\n", + (char*)NULL); + GetEnsembleUsage(ensData, objPtr); + + Tcl_SetObjResult(interp, objPtr); + return TCL_ERROR; +} + + +/* + *---------------------------------------------------------------------- + * + * FreeEnsInvocInternalRep -- + * + * Frees the resources associated with an ensembleInvoc object's + * internal representation. + * + * Results: + * None. + * + * Side effects: + * Decrements the ref count of the two objects referenced by + * this object. If there are no more uses, this will free + * the other objects. + * + *---------------------------------------------------------------------- + */ +static void +FreeEnsInvocInternalRep(objPtr) + register Tcl_Obj *objPtr; /* namespName object with internal + * representation to free */ +{ + Tcl_Obj *prevArgObj = (Tcl_Obj*)objPtr->internalRep.twoPtrValue.ptr2; + + if (prevArgObj) { + Tcl_DecrRefCount(prevArgObj); + } +} + + +/* + *---------------------------------------------------------------------- + * + * DupEnsInvocInternalRep -- + * + * Initializes the internal representation of an ensembleInvoc + * object to a copy of the internal representation of + * another ensembleInvoc object. + * + * This shouldn't be called. Normally, a temporary ensembleInvoc + * object is created while an ensemble call is in progress. + * This object may be converted to string form if an error occurs. + * It does not stay around long, and there is no reason for it + * to be duplicated. + * + * Results: + * None. + * + * Side effects: + * copyPtr's internal rep is set to duplicates of the objects + * pointed to by srcPtr's internal rep. + * + *---------------------------------------------------------------------- + */ +static void +DupEnsInvocInternalRep(srcPtr, copyPtr) + Tcl_Obj *srcPtr; /* Object with internal rep to copy. */ + register Tcl_Obj *copyPtr; /* Object with internal rep to set. */ +{ + EnsemblePart *ensPart = (EnsemblePart*)srcPtr->internalRep.twoPtrValue.ptr1; + Tcl_Obj *prevArgObj = (Tcl_Obj*)srcPtr->internalRep.twoPtrValue.ptr2; + Tcl_Obj *objPtr; + + copyPtr->internalRep.twoPtrValue.ptr1 = (VOID *) ensPart; + + if (prevArgObj) { + objPtr = Tcl_DuplicateObj(prevArgObj); + copyPtr->internalRep.twoPtrValue.ptr2 = (VOID *) objPtr; + } +} + + +/* + *---------------------------------------------------------------------- + * + * SetEnsInvocFromAny -- + * + * Generates the internal representation for an ensembleInvoc + * object. This conversion really shouldn't take place. + * Normally, a temporary ensembleInvoc object is created while + * an ensemble call is in progress. This object may be converted + * to string form if an error occurs. But there is no reason + * for any other object to be converted to ensembleInvoc form. + * + * Results: + * Always returns TCL_OK. + * + * Side effects: + * The string representation is saved as if it were the + * command line argument for the ensemble invocation. The + * reference to the ensemble part is set to NULL. + * + *---------------------------------------------------------------------- + */ +static int +SetEnsInvocFromAny(interp, objPtr) + Tcl_Interp *interp; /* Determines the context for + name resolution */ + register Tcl_Obj *objPtr; /* The object to convert */ +{ + int length; + char *name; + Tcl_Obj *argObj; + + /* + * Get objPtr's string representation. + * Make it up-to-date if necessary. + * THIS FAILS IF THE OBJECT'S STRING REP CONTAINS NULLS. + */ + name = Tcl_GetStringFromObj(objPtr, &length); + + /* + * Make an argument object to contain the string, and + * set the ensemble part definition to NULL. At this point, + * we don't know anything about an ensemble, so we'll just + * keep the string around as if it were the command line + * invocation. + */ + argObj = Tcl_NewStringObj(name, -1); + + /* + * Free the old representation and install a new one. + */ + if (objPtr->typePtr && objPtr->typePtr->freeIntRepProc != NULL) { + (*objPtr->typePtr->freeIntRepProc)(objPtr); + } + objPtr->internalRep.twoPtrValue.ptr1 = (VOID *) NULL; + objPtr->internalRep.twoPtrValue.ptr2 = (VOID *) argObj; + objPtr->typePtr = &itclEnsInvocType; + + return TCL_OK; +} + + +/* + *---------------------------------------------------------------------- + * + * UpdateStringOfEnsInvoc -- + * + * Updates the string representation for an ensembleInvoc object. + * This is called when an error occurs in an ensemble part, when + * the code tries to print objv[0] as the command name. This + * code automatically chains together all of the names leading + * to the ensemble part, so the error message references the + * entire command, not just the part name. + * + * Note: This procedure does not free an existing old string rep + * so storage will be lost if this has not already been done. + * + * Results: + * None. + * + * Side effects: + * The object's string is set to the full command name for + * the ensemble part. + * + *---------------------------------------------------------------------- + */ +static void +UpdateStringOfEnsInvoc(objPtr) + register Tcl_Obj *objPtr; /* NamespName obj to update string rep. */ +{ + EnsemblePart *ensPart = (EnsemblePart*)objPtr->internalRep.twoPtrValue.ptr1; + Tcl_Obj *prevArgObj = (Tcl_Obj*)objPtr->internalRep.twoPtrValue.ptr2; + + Tcl_DString buffer; + int length; + char *name; + + Tcl_DStringInit(&buffer); + + /* + * Get the string representation for the previous argument. + * This will force each ensembleInvoc argument up the line + * to get its string representation. So we will get the + * original command name, followed by the sub-ensemble, and + * the next sub-ensemble, and so on. Then add the part + * name from the ensPart argument. + */ + if (prevArgObj) { + name = Tcl_GetStringFromObj(prevArgObj, &length); + Tcl_DStringAppend(&buffer, name, length); + } + + if (ensPart) { + Tcl_DStringAppendElement(&buffer, ensPart->name); + } + + /* + * The following allocates an empty string on the heap if name is "" + * (e.g., if the internal rep is NULL). + */ + name = Tcl_DStringValue(&buffer); + length = strlen(name); + objPtr->bytes = (char *) ckalloc((unsigned) (length + 1)); + memcpy((VOID *) objPtr->bytes, (VOID *) name, (unsigned) length); + objPtr->bytes[length] = '\0'; + objPtr->length = length; +}
itcl_ensemble.c Property changes : Added: svn:executable ## -0,0 +1 ## +* \ No newline at end of property Index: itcl_linkage.c =================================================================== --- itcl_linkage.c (nonexistent) +++ itcl_linkage.c (revision 1765) @@ -0,0 +1,327 @@ +/* + * ------------------------------------------------------------------------ + * PACKAGE: [incr Tcl] + * DESCRIPTION: Object-Oriented Extensions to Tcl + * + * [incr Tcl] provides object-oriented extensions to Tcl, much as + * C++ provides object-oriented extensions to C. It provides a means + * of encapsulating related procedures together with their shared data + * in a local namespace that is hidden from the outside world. It + * promotes code re-use through inheritance. More than anything else, + * it encourages better organization of Tcl applications through the + * object-oriented paradigm, leading to code that is easier to + * understand and maintain. + * + * This part adds a mechanism for integrating C procedures into + * [incr Tcl] classes as methods and procs. Each C procedure must + * either be declared via Itcl_RegisterC() or dynamically loaded. + * + * ======================================================================== + * AUTHOR: Michael J. McLennan + * Bell Labs Innovations for Lucent Technologies + * mmclennan@lucent.com + * http://www.tcltk.com/itcl + * + * RCS: $Id: itcl_linkage.c,v 1.1.1.1 2002-01-16 10:24:46 markom Exp $ + * ======================================================================== + * Copyright (c) 1993-1998 Lucent Technologies, Inc. + * ------------------------------------------------------------------------ + * See the file "license.terms" for information on usage and redistribution + * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + */ +#include "itclInt.h" + +/* + * These records store the pointers for all "RegisterC" functions. + */ +typedef struct ItclCfunc { + Tcl_CmdProc *argCmdProc; /* old-style (argc,argv) command handler */ + Tcl_ObjCmdProc *objCmdProc; /* new (objc,objv) command handler */ + ClientData clientData; /* client data passed into this function */ + Tcl_CmdDeleteProc *deleteProc; /* proc called to free clientData */ +} ItclCfunc; + +static Tcl_HashTable* ItclGetRegisteredProcs _ANSI_ARGS_((Tcl_Interp *interp)); +static void ItclFreeC _ANSI_ARGS_((ClientData clientData, Tcl_Interp *interp)); + + +/* + * ------------------------------------------------------------------------ + * Itcl_RegisterC() + * + * Used to associate a symbolic name with an (argc,argv) C procedure + * that handles a Tcl command. Procedures that are registered in this + * manner can be referenced in the body of an [incr Tcl] class + * definition to specify C procedures to acting as methods/procs. + * Usually invoked in an initialization routine for an extension, + * called out in Tcl_AppInit() at the start of an application. + * + * Each symbolic procedure can have an arbitrary client data value + * associated with it. This value is passed into the command + * handler whenever it is invoked. + * + * A symbolic procedure name can be used only once for a given style + * (arg/obj) handler. If the name is defined with an arg-style + * handler, it can be redefined with an obj-style handler; or if + * the name is defined with an obj-style handler, it can be redefined + * with an arg-style handler. In either case, any previous client + * data is discarded and the new client data is remembered. However, + * if a name is redefined to a different handler of the same style, + * this procedure returns an error. + * + * Returns TCL_OK on success, or TCL_ERROR (along with an error message + * in interp->result) if anything goes wrong. + * ------------------------------------------------------------------------ + */ +int +Itcl_RegisterC(interp, name, proc, clientData, deleteProc) + Tcl_Interp *interp; /* interpreter handling this registration */ + char *name; /* symbolic name for procedure */ + Tcl_CmdProc *proc; /* procedure handling Tcl command */ + ClientData clientData; /* client data associated with proc */ + Tcl_CmdDeleteProc *deleteProc; /* proc called to free up client data */ +{ + int newEntry; + Tcl_HashEntry *entry; + Tcl_HashTable *procTable; + ItclCfunc *cfunc; + + /* + * Make sure that a proc was specified. + */ + if (!proc) { + Tcl_AppendResult(interp, "initialization error: null pointer for ", + "C procedure \"", name, "\"", + (char*)NULL); + return TCL_ERROR; + } + + /* + * Add a new entry for the given procedure. If an entry with + * this name already exists, then make sure that it was defined + * with the same proc. + */ + procTable = ItclGetRegisteredProcs(interp); + entry = Tcl_CreateHashEntry(procTable, name, &newEntry); + if (!newEntry) { + cfunc = (ItclCfunc*)Tcl_GetHashValue(entry); + if (cfunc->argCmdProc != NULL && cfunc->argCmdProc != proc) { + Tcl_AppendResult(interp, "initialization error: C procedure ", + "with name \"", name, "\" already defined", + (char*)NULL); + return TCL_ERROR; + } + + if (cfunc->deleteProc != NULL) { + (*cfunc->deleteProc)(cfunc->clientData); + } + } + else { + cfunc = (ItclCfunc*)ckalloc(sizeof(ItclCfunc)); + cfunc->objCmdProc = NULL; + } + + cfunc->argCmdProc = proc; + cfunc->clientData = clientData; + cfunc->deleteProc = deleteProc; + + Tcl_SetHashValue(entry, (ClientData)cfunc); + return TCL_OK; +} + + +/* + * ------------------------------------------------------------------------ + * Itcl_RegisterObjC() + * + * Used to associate a symbolic name with an (objc,objv) C procedure + * that handles a Tcl command. Procedures that are registered in this + * manner can be referenced in the body of an [incr Tcl] class + * definition to specify C procedures to acting as methods/procs. + * Usually invoked in an initialization routine for an extension, + * called out in Tcl_AppInit() at the start of an application. + * + * Each symbolic procedure can have an arbitrary client data value + * associated with it. This value is passed into the command + * handler whenever it is invoked. + * + * A symbolic procedure name can be used only once for a given style + * (arg/obj) handler. If the name is defined with an arg-style + * handler, it can be redefined with an obj-style handler; or if + * the name is defined with an obj-style handler, it can be redefined + * with an arg-style handler. In either case, any previous client + * data is discarded and the new client data is remembered. However, + * if a name is redefined to a different handler of the same style, + * this procedure returns an error. + * + * Returns TCL_OK on success, or TCL_ERROR (along with an error message + * in interp->result) if anything goes wrong. + * ------------------------------------------------------------------------ + */ +int +Itcl_RegisterObjC(interp, name, proc, clientData, deleteProc) + Tcl_Interp *interp; /* interpreter handling this registration */ + char *name; /* symbolic name for procedure */ + Tcl_ObjCmdProc *proc; /* procedure handling Tcl command */ + ClientData clientData; /* client data associated with proc */ + Tcl_CmdDeleteProc *deleteProc; /* proc called to free up client data */ +{ + int newEntry; + Tcl_HashEntry *entry; + Tcl_HashTable *procTable; + ItclCfunc *cfunc; + + /* + * Make sure that a proc was specified. + */ + if (!proc) { + Tcl_AppendResult(interp, "initialization error: null pointer for ", + "C procedure \"", name, "\"", + (char*)NULL); + return TCL_ERROR; + } + + /* + * Add a new entry for the given procedure. If an entry with + * this name already exists, then make sure that it was defined + * with the same proc. + */ + procTable = ItclGetRegisteredProcs(interp); + entry = Tcl_CreateHashEntry(procTable, name, &newEntry); + if (!newEntry) { + cfunc = (ItclCfunc*)Tcl_GetHashValue(entry); + if (cfunc->objCmdProc != NULL && cfunc->objCmdProc != proc) { + Tcl_AppendResult(interp, "initialization error: C procedure ", + "with name \"", name, "\" already defined", + (char*)NULL); + return TCL_ERROR; + } + + if (cfunc->deleteProc != NULL) { + (*cfunc->deleteProc)(cfunc->clientData); + } + } + else { + cfunc = (ItclCfunc*)ckalloc(sizeof(ItclCfunc)); + cfunc->argCmdProc = NULL; + } + + cfunc->objCmdProc = proc; + cfunc->clientData = clientData; + cfunc->deleteProc = deleteProc; + + Tcl_SetHashValue(entry, (ClientData)cfunc); + return TCL_OK; +} + + +/* + * ------------------------------------------------------------------------ + * Itcl_FindC() + * + * Used to query a C procedure via its symbolic name. Looks at the + * list of procedures registered previously by either Itcl_RegisterC + * or Itcl_RegisterObjC and returns pointers to the appropriate + * (argc,argv) or (objc,objv) handlers. Returns non-zero if the + * name is recognized and pointers are returned; returns zero + * otherwise. + * ------------------------------------------------------------------------ + */ +int +Itcl_FindC(interp, name, argProcPtr, objProcPtr, cDataPtr) + Tcl_Interp *interp; /* interpreter handling this registration */ + char *name; /* symbolic name for procedure */ + Tcl_CmdProc **argProcPtr; /* returns (argc,argv) command handler */ + Tcl_ObjCmdProc **objProcPtr; /* returns (objc,objv) command handler */ + ClientData *cDataPtr; /* returns client data */ +{ + Tcl_HashEntry *entry; + Tcl_HashTable *procTable; + ItclCfunc *cfunc; + + *argProcPtr = NULL; /* assume info won't be found */ + *objProcPtr = NULL; + *cDataPtr = NULL; + + if (interp) { + procTable = (Tcl_HashTable*)Tcl_GetAssocData(interp, + "itcl_RegC", (Tcl_InterpDeleteProc**)NULL); + + if (procTable) { + entry = Tcl_FindHashEntry(procTable, name); + if (entry) { + cfunc = (ItclCfunc*)Tcl_GetHashValue(entry); + *argProcPtr = cfunc->argCmdProc; + *objProcPtr = cfunc->objCmdProc; + *cDataPtr = cfunc->clientData; + } + } + } + return (*argProcPtr != NULL || *objProcPtr != NULL); +} + + +/* + * ------------------------------------------------------------------------ + * ItclGetRegisteredProcs() + * + * Returns a pointer to a hash table containing the list of registered + * procs in the specified interpreter. If the hash table does not + * already exist, it is created. + * ------------------------------------------------------------------------ + */ +static Tcl_HashTable* +ItclGetRegisteredProcs(interp) + Tcl_Interp *interp; /* interpreter handling this registration */ +{ + Tcl_HashTable* procTable; + + /* + * If the registration table does not yet exist, then create it. + */ + procTable = (Tcl_HashTable*)Tcl_GetAssocData(interp, "itcl_RegC", + (Tcl_InterpDeleteProc**)NULL); + + if (!procTable) { + procTable = (Tcl_HashTable*)ckalloc(sizeof(Tcl_HashTable)); + Tcl_InitHashTable(procTable, TCL_STRING_KEYS); + Tcl_SetAssocData(interp, "itcl_RegC", ItclFreeC, + (ClientData)procTable); + } + return procTable; +} + + +/* + * ------------------------------------------------------------------------ + * ItclFreeC() + * + * When an interpreter is deleted, this procedure is called to + * free up the associated data created by Itcl_RegisterC and + * Itcl_RegisterObjC. + * ------------------------------------------------------------------------ + */ +static void +ItclFreeC(clientData, interp) + ClientData clientData; /* associated data */ + Tcl_Interp *interp; /* intepreter being deleted */ +{ + Tcl_HashTable *tablePtr = (Tcl_HashTable*)clientData; + Tcl_HashSearch place; + Tcl_HashEntry *entry; + ItclCfunc *cfunc; + + entry = Tcl_FirstHashEntry(tablePtr, &place); + while (entry) { + cfunc = (ItclCfunc*)Tcl_GetHashValue(entry); + + if (cfunc->deleteProc != NULL) { + (*cfunc->deleteProc)(cfunc->clientData); + } + ckfree ( (char*)cfunc ); + entry = Tcl_NextHashEntry(&place); + } + + Tcl_DeleteHashTable(tablePtr); + ckfree((char*)tablePtr); +}
itcl_linkage.c Property changes : Added: svn:executable ## -0,0 +1 ## +* \ No newline at end of property Index: itcl_parse.c =================================================================== --- itcl_parse.c (nonexistent) +++ itcl_parse.c (revision 1765) @@ -0,0 +1,1086 @@ +/* + * ------------------------------------------------------------------------ + * PACKAGE: [incr Tcl] + * DESCRIPTION: Object-Oriented Extensions to Tcl + * + * [incr Tcl] provides object-oriented extensions to Tcl, much as + * C++ provides object-oriented extensions to C. It provides a means + * of encapsulating related procedures together with their shared data + * in a local namespace that is hidden from the outside world. It + * promotes code re-use through inheritance. More than anything else, + * it encourages better organization of Tcl applications through the + * object-oriented paradigm, leading to code that is easier to + * understand and maintain. + * + * Procedures in this file support the new syntax for [incr Tcl] + * class definitions: + * + * itcl_class { + * inherit ... + * + * constructor {} ?{}? {} + * destructor {} + * + * method {} {} + * proc {} {} + * variable ?? ?? + * common ?? + * + * public ?...? + * protected ?...? + * private ?...? + * } + * + * ======================================================================== + * AUTHOR: Michael J. McLennan + * Bell Labs Innovations for Lucent Technologies + * mmclennan@lucent.com + * http://www.tcltk.com/itcl + * + * RCS: $Id: itcl_parse.c,v 1.1.1.1 2002-01-16 10:24:46 markom Exp $ + * ======================================================================== + * Copyright (c) 1993-1998 Lucent Technologies, Inc. + * ------------------------------------------------------------------------ + * See the file "license.terms" for information on usage and redistribution + * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + */ +#include "itclInt.h" + +/* + * Info needed for public/protected/private commands: + */ +typedef struct ProtectionCmdInfo { + int pLevel; /* protection level */ + ItclObjectInfo *info; /* info regarding all known objects */ +} ProtectionCmdInfo; + +/* + * FORWARD DECLARATIONS + */ +static void ItclFreeParserCommandData _ANSI_ARGS_((char* cdata)); + + +/* + * ------------------------------------------------------------------------ + * Itcl_ParseInit() + * + * Invoked by Itcl_Init() whenever a new interpeter is created to add + * [incr Tcl] facilities. Adds the commands needed to parse class + * definitions. + * ------------------------------------------------------------------------ + */ +int +Itcl_ParseInit(interp, info) + Tcl_Interp *interp; /* interpreter to be updated */ + ItclObjectInfo *info; /* info regarding all known objects */ +{ + Tcl_Namespace *parserNs; + ProtectionCmdInfo *pInfo; + + /* + * Create the "itcl::parser" namespace used to parse class + * definitions. + */ + parserNs = Tcl_CreateNamespace(interp, "::itcl::parser", + (ClientData)info, Itcl_ReleaseData); + + if (!parserNs) { + Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), + " (cannot initialize itcl parser)", + (char*)NULL); + return TCL_ERROR; + } + Itcl_PreserveData((ClientData)info); + + /* + * Add commands for parsing class definitions. + */ + Tcl_CreateObjCommand(interp, "::itcl::parser::inherit", + Itcl_ClassInheritCmd, (ClientData)info, (Tcl_CmdDeleteProc*)NULL); + + Tcl_CreateObjCommand(interp, "::itcl::parser::constructor", + Itcl_ClassConstructorCmd, (ClientData)info, (Tcl_CmdDeleteProc*)NULL); + + Tcl_CreateObjCommand(interp, "::itcl::parser::destructor", + Itcl_ClassDestructorCmd, (ClientData)info, (Tcl_CmdDeleteProc*)NULL); + + Tcl_CreateObjCommand(interp, "::itcl::parser::method", + Itcl_ClassMethodCmd, (ClientData)info, (Tcl_CmdDeleteProc*)NULL); + + Tcl_CreateObjCommand(interp, "::itcl::parser::proc", + Itcl_ClassProcCmd, (ClientData)info, (Tcl_CmdDeleteProc*)NULL); + + Tcl_CreateObjCommand(interp, "::itcl::parser::common", + Itcl_ClassCommonCmd, (ClientData)info, (Tcl_CmdDeleteProc*)NULL); + + Tcl_CreateObjCommand(interp, "::itcl::parser::variable", + Itcl_ClassVariableCmd, (ClientData)info, (Tcl_CmdDeleteProc*)NULL); + + pInfo = (ProtectionCmdInfo*)ckalloc(sizeof(ProtectionCmdInfo)); + pInfo->pLevel = ITCL_PUBLIC; + pInfo->info = info; + + Tcl_CreateObjCommand(interp, "::itcl::parser::public", + Itcl_ClassProtectionCmd, (ClientData)pInfo, + (Tcl_CmdDeleteProc*) ItclFreeParserCommandData); + + pInfo = (ProtectionCmdInfo*)ckalloc(sizeof(ProtectionCmdInfo)); + pInfo->pLevel = ITCL_PROTECTED; + pInfo->info = info; + + Tcl_CreateObjCommand(interp, "::itcl::parser::protected", + Itcl_ClassProtectionCmd, (ClientData)pInfo, + (Tcl_CmdDeleteProc*) ItclFreeParserCommandData); + + pInfo = (ProtectionCmdInfo*)ckalloc(sizeof(ProtectionCmdInfo)); + pInfo->pLevel = ITCL_PRIVATE; + pInfo->info = info; + + Tcl_CreateObjCommand(interp, "::itcl::parser::private", + Itcl_ClassProtectionCmd, (ClientData)pInfo, + (Tcl_CmdDeleteProc*) ItclFreeParserCommandData); + + /* + * Set the runtime variable resolver for the parser namespace, + * to control access to "common" data members while parsing + * the class definition. + */ + Tcl_SetNamespaceResolvers(parserNs, (Tcl_ResolveCmdProc*)NULL, + Itcl_ParseVarResolver, (Tcl_ResolveCompiledVarProc*)NULL); + + /* + * Install the "class" command for defining new classes. + */ + Tcl_CreateObjCommand(interp, "::itcl::class", Itcl_ClassCmd, + (ClientData)info, Itcl_ReleaseData); + Itcl_PreserveData((ClientData)info); + + return TCL_OK; +} + + +/* + * ------------------------------------------------------------------------ + * Itcl_ClassCmd() + * + * Invoked by Tcl whenever the user issues an "itcl::class" command to + * specify a class definition. Handles the following syntax: + * + * itcl::class { + * inherit ... + * + * constructor {} ?{}? {} + * destructor {} + * + * method {} {} + * proc {} {} + * variable ?? ?? + * common ?? + * + * public ... + * protected ... + * private ... + * } + * + * ------------------------------------------------------------------------ + */ +int +Itcl_ClassCmd(clientData, interp, objc, objv) + ClientData clientData; /* info for all known objects */ + Tcl_Interp *interp; /* current interpreter */ + int objc; /* number of arguments */ + Tcl_Obj *CONST objv[]; /* argument objects */ +{ + ItclObjectInfo* info = (ItclObjectInfo*)clientData; + + int result; + char *className; + Tcl_Namespace *parserNs; + ItclClass *cdefnPtr; + Tcl_CallFrame frame; + + if (objc != 3) { + Tcl_WrongNumArgs(interp, 1, objv, "name { definition }"); + return TCL_ERROR; + } + className = Tcl_GetStringFromObj(objv[1], (int*)NULL); + + /* + * Find the namespace to use as a parser for the class definition. + * If for some reason it is destroyed, bail out here. + */ + parserNs = Tcl_FindNamespace(interp, "::itcl::parser", + (Tcl_Namespace*)NULL, TCL_LEAVE_ERR_MSG); + + if (parserNs == NULL) { + char msg[256]; + sprintf(msg, "\n (while parsing class definition for \"%.100s\")", + className); + Tcl_AddErrorInfo(interp, msg); + return TCL_ERROR; + } + + /* + * Try to create the specified class and its namespace. + */ + if (Itcl_CreateClass(interp, className, info, &cdefnPtr) != TCL_OK) { + return TCL_ERROR; + } + + /* + * Import the built-in commands from the itcl::builtin namespace. + * Do this before parsing the class definition, so methods/procs + * can override the built-in commands. + */ + result = Tcl_Import(interp, cdefnPtr->namesp, "::itcl::builtin::*", + /* allowOverwrite */ 1); + + if (result != TCL_OK) { + char msg[256]; + sprintf(msg, "\n (while installing built-in commands for class \"%.100s\")", className); + Tcl_AddErrorInfo(interp, msg); + + Tcl_DeleteNamespace(cdefnPtr->namesp); + return TCL_ERROR; + } + + /* + * Push this class onto the class definition stack so that it + * becomes the current context for all commands in the parser. + * Activate the parser and evaluate the class definition. + */ + Itcl_PushStack((ClientData)cdefnPtr, &info->cdefnStack); + + result = Tcl_PushCallFrame(interp, &frame, parserNs, + /* isProcCallFrame */ 0); + + if (result == TCL_OK) { + /* CYGNUS LOCAL - Fix for Tcl8.1 */ +#if TCL_MAJOR_VERSION == 8 && TCL_MINOR_VERSION == 0 + result = Tcl_EvalObj(interp, objv[2]); +#else + result = Tcl_EvalObj(interp, objv[2], 0); +#endif + /* END CYGNUS LOCAL */ + Tcl_PopCallFrame(interp); + } + Itcl_PopStack(&info->cdefnStack); + + if (result != TCL_OK) { + char msg[256]; + sprintf(msg, "\n (class \"%.200s\" body line %d)", + className, interp->errorLine); + Tcl_AddErrorInfo(interp, msg); + + Tcl_DeleteNamespace(cdefnPtr->namesp); + return TCL_ERROR; + } + + /* + * At this point, parsing of the class definition has succeeded. + * Add built-in methods such as "configure" and "cget"--as long + * as they don't conflict with those defined in the class. + */ + if (Itcl_InstallBiMethods(interp, cdefnPtr) != TCL_OK) { + Tcl_DeleteNamespace(cdefnPtr->namesp); + return TCL_ERROR; + } + + /* + * Build the name resolution tables for all data members. + */ + Itcl_BuildVirtualTables(cdefnPtr); + + Tcl_ResetResult(interp); + return TCL_OK; +} + + +/* + * ------------------------------------------------------------------------ + * Itcl_ClassInheritCmd() + * + * Invoked by Tcl during the parsing of a class definition whenever + * the "inherit" command is invoked to define one or more base classes. + * Handles the following syntax: + * + * inherit ?...? + * + * ------------------------------------------------------------------------ + */ +int +Itcl_ClassInheritCmd(clientData, interp, objc, objv) + ClientData clientData; /* info for all known objects */ + Tcl_Interp *interp; /* current interpreter */ + int objc; /* number of arguments */ + Tcl_Obj *CONST objv[]; /* argument objects */ +{ + ItclObjectInfo *info = (ItclObjectInfo*)clientData; + ItclClass *cdefnPtr = (ItclClass*)Itcl_PeekStack(&info->cdefnStack); + + int result, i, newEntry; + char *token; + Itcl_ListElem *elem, *elem2; + ItclClass *cdPtr, *baseCdefnPtr, *badCdPtr; + ItclHierIter hier; + Itcl_Stack stack; + Tcl_CallFrame frame; + + if (objc < 2) { + Tcl_WrongNumArgs(interp, 1, objv, "class ?class...?"); + return TCL_ERROR; + } + + /* + * In "inherit" statement can only be included once in a + * class definition. + */ + elem = Itcl_FirstListElem(&cdefnPtr->bases); + if (elem != NULL) { + Tcl_AppendToObj(Tcl_GetObjResult(interp), "inheritance \"", -1); + + while (elem) { + cdPtr = (ItclClass*)Itcl_GetListValue(elem); + Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), + cdPtr->name, " ", (char*)NULL); + + elem = Itcl_NextListElem(elem); + } + + Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), + "\" already defined for class \"", cdefnPtr->fullname, "\"", + (char*)NULL); + return TCL_ERROR; + } + + /* + * Validate each base class and add it to the "bases" list. + */ + result = Tcl_PushCallFrame(interp, &frame, cdefnPtr->namesp->parentPtr, + /* isProcCallFrame */ 0); + + if (result != TCL_OK) { + return TCL_ERROR; + } + + for (objc--,objv++; objc > 0; objc--,objv++) { + + /* + * Make sure that the base class name is known in the + * parent namespace (currently active). If not, try + * to autoload its definition. + */ + token = Tcl_GetStringFromObj(*objv, (int*)NULL); + baseCdefnPtr = Itcl_FindClass(interp, token, /* autoload */ 1); + if (!baseCdefnPtr) { + Tcl_Obj *resultPtr = Tcl_GetObjResult(interp); + int errlen; + char *errmsg; + + Tcl_IncrRefCount(resultPtr); + errmsg = Tcl_GetStringFromObj(resultPtr, &errlen); + + Tcl_ResetResult(interp); + Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), + "cannot inherit from \"", token, "\"", + (char*)NULL); + + if (errlen > 0) { + Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), + " (", errmsg, ")", (char*)NULL); + } + Tcl_DecrRefCount(resultPtr); + goto inheritError; + } + + /* + * Make sure that the base class is not the same as the + * class that is being built. + */ + if (baseCdefnPtr == cdefnPtr) { + Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), + "class \"", cdefnPtr->name, "\" cannot inherit from itself", + (char*)NULL); + goto inheritError; + } + + Itcl_AppendList(&cdefnPtr->bases, (ClientData)baseCdefnPtr); + Itcl_PreserveData((ClientData)baseCdefnPtr); + } + + /* + * Scan through the inheritance list to make sure that no + * class appears twice. + */ + elem = Itcl_FirstListElem(&cdefnPtr->bases); + while (elem) { + elem2 = Itcl_NextListElem(elem); + while (elem2) { + if (Itcl_GetListValue(elem) == Itcl_GetListValue(elem2)) { + cdPtr = (ItclClass*)Itcl_GetListValue(elem); + Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), + "class \"", cdefnPtr->fullname, + "\" cannot inherit base class \"", + cdPtr->fullname, "\" more than once", + (char*)NULL); + goto inheritError; + } + elem2 = Itcl_NextListElem(elem2); + } + elem = Itcl_NextListElem(elem); + } + + /* + * Add each base class and all of its base classes into + * the heritage for the current class. Along the way, make + * sure that no class appears twice in the heritage. + */ + Itcl_InitHierIter(&hier, cdefnPtr); + cdPtr = Itcl_AdvanceHierIter(&hier); /* skip the class itself */ + cdPtr = Itcl_AdvanceHierIter(&hier); + while (cdPtr != NULL) { + (void) Tcl_CreateHashEntry(&cdefnPtr->heritage, + (char*)cdPtr, &newEntry); + + if (!newEntry) { + break; + } + cdPtr = Itcl_AdvanceHierIter(&hier); + } + Itcl_DeleteHierIter(&hier); + + /* + * Same base class found twice in the hierarchy? + * Then flag error. Show the list of multiple paths + * leading to the same base class. + */ + if (!newEntry) { + Tcl_Obj *resultPtr = Tcl_GetObjResult(interp); + + badCdPtr = cdPtr; + Tcl_AppendStringsToObj(resultPtr, + "class \"", cdefnPtr->fullname, "\" inherits base class \"", + badCdPtr->fullname, "\" more than once:", + (char*)NULL); + + cdPtr = cdefnPtr; + Itcl_InitStack(&stack); + Itcl_PushStack((ClientData)cdPtr, &stack); + + /* + * Show paths leading to bad base class + */ + while (Itcl_GetStackSize(&stack) > 0) { + cdPtr = (ItclClass*)Itcl_PopStack(&stack); + + if (cdPtr == badCdPtr) { + Tcl_AppendToObj(resultPtr, "\n ", -1); + for (i=0; i < Itcl_GetStackSize(&stack); i++) { + if (Itcl_GetStackValue(&stack, i) == NULL) { + cdPtr = (ItclClass*)Itcl_GetStackValue(&stack, i-1); + Tcl_AppendStringsToObj(resultPtr, + cdPtr->name, "->", + (char*)NULL); + } + } + Tcl_AppendToObj(resultPtr, badCdPtr->name, -1); + } + else if (!cdPtr) { + (void)Itcl_PopStack(&stack); + } + else { + elem = Itcl_LastListElem(&cdPtr->bases); + if (elem) { + Itcl_PushStack((ClientData)cdPtr, &stack); + Itcl_PushStack((ClientData)NULL, &stack); + while (elem) { + Itcl_PushStack(Itcl_GetListValue(elem), &stack); + elem = Itcl_PrevListElem(elem); + } + } + } + } + Itcl_DeleteStack(&stack); + goto inheritError; + } + + /* + * At this point, everything looks good. + * Finish the installation of the base classes. Update + * each base class to recognize the current class as a + * derived class. + */ + elem = Itcl_FirstListElem(&cdefnPtr->bases); + while (elem) { + baseCdefnPtr = (ItclClass*)Itcl_GetListValue(elem); + + Itcl_AppendList(&baseCdefnPtr->derived, (ClientData)cdefnPtr); + Itcl_PreserveData((ClientData)cdefnPtr); + + elem = Itcl_NextListElem(elem); + } + + Tcl_PopCallFrame(interp); + return TCL_OK; + + + /* + * If the "inherit" list cannot be built properly, tear it + * down and return an error. + */ +inheritError: + Tcl_PopCallFrame(interp); + + elem = Itcl_FirstListElem(&cdefnPtr->bases); + while (elem) { + Itcl_ReleaseData( Itcl_GetListValue(elem) ); + elem = Itcl_DeleteListElem(elem); + } + return TCL_ERROR; +} + + +/* + * ------------------------------------------------------------------------ + * Itcl_ClassProtectionCmd() + * + * Invoked by Tcl whenever the user issues a protection setting + * command like "public" or "private". Creates commands and + * variables, and assigns a protection level to them. Protection + * levels are defined as follows: + * + * public => accessible from any namespace + * protected => accessible from selected namespaces + * private => accessible only in the namespace where it was defined + * + * Handles the following syntax: + * + * public ? ...? + * + * Returns TCL_OK/TCL_ERROR to indicate success/failure. + * ------------------------------------------------------------------------ + */ +int +Itcl_ClassProtectionCmd(clientData, interp, objc, objv) + ClientData clientData; /* protection level (public/protected/private) */ + Tcl_Interp *interp; /* current interpreter */ + int objc; /* number of arguments */ + Tcl_Obj *CONST objv[]; /* argument objects */ +{ + ProtectionCmdInfo *pInfo = (ProtectionCmdInfo*)clientData; + + int result; + int oldLevel; + + if (objc < 2) { + Tcl_WrongNumArgs(interp, 1, objv, "command ?arg arg...?"); + return TCL_ERROR; + } + + oldLevel = Itcl_Protection(interp, pInfo->pLevel); + + if (objc == 2) { + /* CYGNUS LOCAL - Fix for Tcl8.1 */ +#if TCL_MAJOR_VERSION == 8 && TCL_MINOR_VERSION == 0 + result = Tcl_EvalObj(interp, objv[1]); +#else + result = Tcl_EvalObj(interp, objv[1], 0); +#endif + /* END CYGNUS LOCAL */ + } else { + result = Itcl_EvalArgs(interp, objc-1, objv+1); + } + + if (result == TCL_BREAK) { + Tcl_SetResult(interp, "invoked \"break\" outside of a loop", + TCL_STATIC); + result = TCL_ERROR; + } + else if (result == TCL_CONTINUE) { + Tcl_SetResult(interp, "invoked \"continue\" outside of a loop", + TCL_STATIC); + result = TCL_ERROR; + } + else if (result != TCL_OK) { + char mesg[256], *token; + token = Tcl_GetStringFromObj(objv[0], (int*)NULL); + sprintf(mesg, "\n (%.100s body line %d)", token, interp->errorLine); + Tcl_AddErrorInfo(interp, mesg); + } + + Itcl_Protection(interp, oldLevel); + return result; +} + + +/* + * ------------------------------------------------------------------------ + * Itcl_ClassConstructorCmd() + * + * Invoked by Tcl during the parsing of a class definition whenever + * the "constructor" command is invoked to define the constructor + * for an object. Handles the following syntax: + * + * constructor ?? + * + * ------------------------------------------------------------------------ + */ +int +Itcl_ClassConstructorCmd(clientData, interp, objc, objv) + ClientData clientData; /* info for all known objects */ + Tcl_Interp *interp; /* current interpreter */ + int objc; /* number of arguments */ + Tcl_Obj *CONST objv[]; /* argument objects */ +{ + ItclObjectInfo *info = (ItclObjectInfo*)clientData; + ItclClass *cdefnPtr = (ItclClass*)Itcl_PeekStack(&info->cdefnStack); + + char *name, *arglist, *body; + + if (objc < 3 || objc > 4) { + Tcl_WrongNumArgs(interp, 1, objv, "args ?init? body"); + return TCL_ERROR; + } + + name = Tcl_GetStringFromObj(objv[0], (int*)NULL); + if (Tcl_FindHashEntry(&cdefnPtr->functions, name)) { + Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), + "\"", name, "\" already defined in class \"", + cdefnPtr->fullname, "\"", + (char*)NULL); + return TCL_ERROR; + } + + /* + * If there is an object initialization statement, pick this + * out and take the last argument as the constructor body. + */ + arglist = Tcl_GetStringFromObj(objv[1], (int*)NULL); + if (objc == 3) { + body = Tcl_GetStringFromObj(objv[2], (int*)NULL); + } else { + cdefnPtr->initCode = objv[2]; + Tcl_IncrRefCount(cdefnPtr->initCode); + body = Tcl_GetStringFromObj(objv[3], (int*)NULL); + } + + if (Itcl_CreateMethod(interp, cdefnPtr, name, arglist, body) != TCL_OK) { + return TCL_ERROR; + } + return TCL_OK; +} + + +/* + * ------------------------------------------------------------------------ + * Itcl_ClassDestructorCmd() + * + * Invoked by Tcl during the parsing of a class definition whenever + * the "destructor" command is invoked to define the destructor + * for an object. Handles the following syntax: + * + * destructor + * + * ------------------------------------------------------------------------ + */ +int +Itcl_ClassDestructorCmd(clientData, interp, objc, objv) + ClientData clientData; /* info for all known objects */ + Tcl_Interp *interp; /* current interpreter */ + int objc; /* number of arguments */ + Tcl_Obj *CONST objv[]; /* argument objects */ +{ + ItclObjectInfo *info = (ItclObjectInfo*)clientData; + ItclClass *cdefnPtr = (ItclClass*)Itcl_PeekStack(&info->cdefnStack); + + char *name, *body; + + if (objc != 2) { + Tcl_WrongNumArgs(interp, 1, objv, "body"); + return TCL_ERROR; + } + + name = Tcl_GetStringFromObj(objv[0], (int*)NULL); + body = Tcl_GetStringFromObj(objv[1], (int*)NULL); + + if (Tcl_FindHashEntry(&cdefnPtr->functions, name)) { + Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), + "\"", name, "\" already defined in class \"", + cdefnPtr->fullname, "\"", + (char*)NULL); + return TCL_ERROR; + } + + if (Itcl_CreateMethod(interp, cdefnPtr, name, (char*)NULL, body) + != TCL_OK) { + return TCL_ERROR; + } + return TCL_OK; +} + +/* + * ------------------------------------------------------------------------ + * Itcl_ClassMethodCmd() + * + * Invoked by Tcl during the parsing of a class definition whenever + * the "method" command is invoked to define an object method. + * Handles the following syntax: + * + * method ?? ?? + * + * ------------------------------------------------------------------------ + */ +int +Itcl_ClassMethodCmd(clientData, interp, objc, objv) + ClientData clientData; /* info for all known objects */ + Tcl_Interp *interp; /* current interpreter */ + int objc; /* number of arguments */ + Tcl_Obj *CONST objv[]; /* argument objects */ +{ + ItclObjectInfo *info = (ItclObjectInfo*)clientData; + ItclClass *cdefnPtr = (ItclClass*)Itcl_PeekStack(&info->cdefnStack); + + char *name, *arglist, *body; + + if (objc < 2 || objc > 4) { + Tcl_WrongNumArgs(interp, 1, objv, "name ?args? ?body?"); + return TCL_ERROR; + } + + name = Tcl_GetStringFromObj(objv[1], (int*)NULL); + + arglist = NULL; + body = NULL; + if (objc >= 3) { + arglist = Tcl_GetStringFromObj(objv[2], (int*)NULL); + } + if (objc >= 4) { + body = Tcl_GetStringFromObj(objv[3], (int*)NULL); + } + + if (Itcl_CreateMethod(interp, cdefnPtr, name, arglist, body) != TCL_OK) { + return TCL_ERROR; + } + return TCL_OK; +} + + +/* + * ------------------------------------------------------------------------ + * Itcl_ClassProcCmd() + * + * Invoked by Tcl during the parsing of a class definition whenever + * the "proc" command is invoked to define a common class proc. + * A "proc" is like a "method", but only has access to "common" + * class variables. Handles the following syntax: + * + * proc ?? ?? + * + * ------------------------------------------------------------------------ + */ +int +Itcl_ClassProcCmd(clientData, interp, objc, objv) + ClientData clientData; /* info for all known objects */ + Tcl_Interp *interp; /* current interpreter */ + int objc; /* number of arguments */ + Tcl_Obj *CONST objv[]; /* argument objects */ +{ + ItclObjectInfo *info = (ItclObjectInfo*)clientData; + ItclClass *cdefnPtr = (ItclClass*)Itcl_PeekStack(&info->cdefnStack); + char *name, *arglist, *body; + + if (objc < 2 || objc > 4) { + Tcl_WrongNumArgs(interp, 1, objv, "name ?args? ?body?"); + return TCL_ERROR; + } + + name = Tcl_GetStringFromObj(objv[1], (int*)NULL); + + arglist = NULL; + body = NULL; + if (objc >= 3) { + arglist = Tcl_GetStringFromObj(objv[2], (int*)NULL); + } + if (objc >= 4) { + body = Tcl_GetStringFromObj(objv[3], (int*)NULL); + } + + if (Itcl_CreateProc(interp, cdefnPtr, name, arglist, body) != TCL_OK) { + return TCL_ERROR; + } + return TCL_OK; +} + + +/* + * ------------------------------------------------------------------------ + * Itcl_ClassVariableCmd() + * + * Invoked by Tcl during the parsing of a class definition whenever + * the "variable" command is invoked to define an instance variable. + * Handles the following syntax: + * + * variable ?? ?? + * + * ------------------------------------------------------------------------ + */ +int +Itcl_ClassVariableCmd(clientData, interp, objc, objv) + ClientData clientData; /* info for all known objects */ + Tcl_Interp *interp; /* current interpreter */ + int objc; /* number of arguments */ + Tcl_Obj *CONST objv[]; /* argument objects */ +{ + ItclObjectInfo *info = (ItclObjectInfo*)clientData; + ItclClass *cdefnPtr = (ItclClass*)Itcl_PeekStack(&info->cdefnStack); + + int pLevel; + ItclVarDefn *vdefn; + char *name, *init, *config; + + pLevel = Itcl_Protection(interp, 0); + + if (pLevel == ITCL_PUBLIC) { + if (objc < 2 || objc > 4) { + Tcl_WrongNumArgs(interp, 1, objv, "name ?init? ?config?"); + return TCL_ERROR; + } + } + else if ((objc < 2) || (objc > 3)) { + Tcl_WrongNumArgs(interp, 1, objv, "name ?init?"); + return TCL_ERROR; + } + + /* + * Make sure that the variable name does not contain anything + * goofy like a "::" scope qualifier. + */ + name = Tcl_GetStringFromObj(objv[1], (int*)NULL); + if (strstr(name, "::")) { + Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), + "bad variable name \"", name, "\"", + (char*)NULL); + return TCL_ERROR; + } + + init = NULL; + config = NULL; + if (objc >= 3) { + init = Tcl_GetStringFromObj(objv[2], (int*)NULL); + } + if (objc >= 4) { + config = Tcl_GetStringFromObj(objv[3], (int*)NULL); + } + + if (Itcl_CreateVarDefn(interp, cdefnPtr, name, init, config, + &vdefn) != TCL_OK) { + + return TCL_ERROR; + } + + return TCL_OK; +} + + +/* + * ------------------------------------------------------------------------ + * Itcl_ClassCommonCmd() + * + * Invoked by Tcl during the parsing of a class definition whenever + * the "common" command is invoked to define a variable that is + * common to all objects in the class. Handles the following syntax: + * + * common ?? + * + * ------------------------------------------------------------------------ + */ +int +Itcl_ClassCommonCmd(clientData, interp, objc, objv) + ClientData clientData; /* info for all known objects */ + Tcl_Interp *interp; /* current interpreter */ + int objc; /* number of arguments */ + Tcl_Obj *CONST objv[]; /* argument objects */ +{ + ItclObjectInfo *info = (ItclObjectInfo*)clientData; + ItclClass *cdefnPtr = (ItclClass*)Itcl_PeekStack(&info->cdefnStack); + + int newEntry; + char *name, *init; + ItclVarDefn *vdefn; + Tcl_HashEntry *entry; + Namespace *nsPtr; + Var *varPtr; + + if ((objc < 2) || (objc > 3)) { + Tcl_WrongNumArgs(interp, 1, objv, "varname ?init?"); + return TCL_ERROR; + } + + /* + * Make sure that the variable name does not contain anything + * goofy like a "::" scope qualifier. + */ + name = Tcl_GetStringFromObj(objv[1], (int*)NULL); + if (strstr(name, "::")) { + Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), + "bad variable name \"", name, "\"", + (char*)NULL); + return TCL_ERROR; + } + + init = NULL; + if (objc >= 3) { + init = Tcl_GetStringFromObj(objv[2], (int*)NULL); + } + + if (Itcl_CreateVarDefn(interp, cdefnPtr, name, init, (char*)NULL, + &vdefn) != TCL_OK) { + + return TCL_ERROR; + } + vdefn->member->flags |= ITCL_COMMON; + + /* + * Create the variable in the namespace associated with the + * class. Do this the hard way, to avoid the variable resolver + * procedures. These procedures won't work until we rebuild + * the virtual tables below. + */ + nsPtr = (Namespace*)cdefnPtr->namesp; + entry = Tcl_CreateHashEntry(&nsPtr->varTable, + vdefn->member->name, &newEntry); + + varPtr = _TclNewVar(); + varPtr->hPtr = entry; + varPtr->nsPtr = nsPtr; + varPtr->flags |= VAR_NAMESPACE_VAR; + varPtr->refCount++; /* one use by namespace */ + varPtr->refCount++; /* another use by class */ + + Tcl_SetHashValue(entry, varPtr); + + /* + * TRICKY NOTE: Make sure to rebuild the virtual tables for this + * class so that this variable is ready to access. The variable + * resolver for the parser namespace needs this info to find the + * variable if the developer tries to set it within the class + * definition. + * + * If an initialization value was specified, then initialize + * the variable now. + */ + Itcl_BuildVirtualTables(cdefnPtr); + + if (init) { + init = Tcl_SetVar(interp, vdefn->member->name, init, + TCL_NAMESPACE_ONLY); + + if (!init) { + Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), + "cannot initialize common variable \"", + vdefn->member->name, "\"", + (char*)NULL); + return TCL_ERROR; + } + } + return TCL_OK; +} + + +/* + * ------------------------------------------------------------------------ + * Itcl_ParseVarResolver() + * + * Used by the "parser" namespace to resolve variable accesses to + * common variables. The runtime resolver procedure is consulted + * whenever a variable is accessed within the namespace. It can + * deny access to certain variables, or perform special lookups itself. + * + * This procedure allows access only to "common" class variables that + * have been declared within the class or inherited from another class. + * A "set" command can be used to initialized common data members within + * the body of the class definition itself: + * + * itcl::class Foo { + * common colors + * set colors(red) #ff0000 + * set colors(green) #00ff00 + * set colors(blue) #0000ff + * ... + * } + * + * itcl::class Bar { + * inherit Foo + * set colors(gray) #a0a0a0 + * set colors(white) #ffffff + * + * common numbers + * set numbers(0) zero + * set numbers(1) one + * } + * + * ------------------------------------------------------------------------ + */ +/* ARGSUSED */ +int +Itcl_ParseVarResolver(interp, name, contextNs, flags, rPtr) + Tcl_Interp *interp; /* current interpreter */ + char* name; /* name of the variable being accessed */ + Tcl_Namespace *contextNs; /* namespace context */ + int flags; /* TCL_GLOBAL_ONLY => global variable + * TCL_NAMESPACE_ONLY => namespace variable */ + Tcl_Var* rPtr; /* returns: Tcl_Var for desired variable */ +{ + ItclObjectInfo *info = (ItclObjectInfo*)contextNs->clientData; + ItclClass *cdefnPtr = (ItclClass*)Itcl_PeekStack(&info->cdefnStack); + + Tcl_HashEntry *entry; + ItclVarLookup *vlookup; + + /* + * See if the requested variable is a recognized "common" member. + * If it is, make sure that access is allowed. + */ + entry = Tcl_FindHashEntry(&cdefnPtr->resolveVars, name); + if (entry) { + vlookup = (ItclVarLookup*)Tcl_GetHashValue(entry); + + if ((vlookup->vdefn->member->flags & ITCL_COMMON) != 0) { + if (!vlookup->accessible) { + Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), + "can't access \"", name, "\": ", + Itcl_ProtectionStr(vlookup->vdefn->member->protection), + " variable", + (char*)NULL); + return TCL_ERROR; + } + *rPtr = vlookup->var.common; + return TCL_OK; + } + } + + /* + * If the variable is not recognized, return TCL_CONTINUE and + * let lookup continue via the normal name resolution rules. + * This is important for variables like "errorInfo" + * that might get set while the parser namespace is active. + */ + return TCL_CONTINUE; +} + +/* + * ------------------------------------------------------------------------ + * ItclFreeParserCommandData() + * + * This callback will free() up memory dynamically allocated + * and passed as the ClientData argument to Tcl_CreateObjCommand. + * This callback is required because one can not simply pass + * a pointer to the free() or ckfree() to Tcl_CreateObjCommand. + * ------------------------------------------------------------------------ + */ +static void +ItclFreeParserCommandData(cdata) + char* cdata; /* client data to be destroyed */ +{ + ckfree(cdata); +}
itcl_parse.c Property changes : Added: svn:executable ## -0,0 +1 ## +* \ No newline at end of property Index: itcl_obsolete.c =================================================================== --- itcl_obsolete.c (nonexistent) +++ itcl_obsolete.c (revision 1765) @@ -0,0 +1,1959 @@ +/* + * ------------------------------------------------------------------------ + * PACKAGE: [incr Tcl] + * DESCRIPTION: Object-Oriented Extensions to Tcl + * + * [incr Tcl] provides object-oriented extensions to Tcl, much as + * C++ provides object-oriented extensions to C. It provides a means + * of encapsulating related procedures together with their shared data + * in a local namespace that is hidden from the outside world. It + * promotes code re-use through inheritance. More than anything else, + * it encourages better organization of Tcl applications through the + * object-oriented paradigm, leading to code that is easier to + * understand and maintain. + * + * Procedures in this file support the old-style syntax for [incr Tcl] + * class definitions: + * + * itcl_class { + * inherit ... + * + * constructor {} { } + * destructor { } + * + * method {} { } + * proc {} { } + * + * public ?? ?? + * protected ?? + * common ?? + * } + * + * This capability will be removed in a future release, after users + * have had a chance to switch over to the new syntax. + * + * ======================================================================== + * AUTHOR: Michael J. McLennan + * Bell Labs Innovations for Lucent Technologies + * mmclennan@lucent.com + * http://www.tcltk.com/itcl + * + * RCS: $Id: itcl_obsolete.c,v 1.1.1.1 2002-01-16 10:24:46 markom Exp $ + * ======================================================================== + * Copyright (c) 1993-1998 Lucent Technologies, Inc. + * ------------------------------------------------------------------------ + * See the file "license.terms" for information on usage and redistribution + * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + */ +#include "itclInt.h" + +/* + * FORWARD DECLARATIONS + */ +static int ItclOldClassCmd _ANSI_ARGS_((ClientData cdata, + Tcl_Interp *interp, int objc, Tcl_Obj* CONST objv[])); + +static int ItclOldMethodCmd _ANSI_ARGS_((ClientData cdata, + Tcl_Interp *interp, int objc, Tcl_Obj* CONST objv[])); +static int ItclOldPublicCmd _ANSI_ARGS_((ClientData cdata, + Tcl_Interp *interp, int objc, Tcl_Obj* CONST objv[])); +static int ItclOldProtectedCmd _ANSI_ARGS_((ClientData cdata, + Tcl_Interp *interp, int objc, Tcl_Obj* CONST objv[])); +static int ItclOldCommonCmd _ANSI_ARGS_((ClientData cdata, + Tcl_Interp *interp, int objc, Tcl_Obj* CONST objv[])); + +static int ItclOldBiDeleteCmd _ANSI_ARGS_((ClientData cdata, + Tcl_Interp *interp, int objc, Tcl_Obj* CONST objv[])); +static int ItclOldBiVirtualCmd _ANSI_ARGS_((ClientData cdata, + Tcl_Interp *interp, int objc, Tcl_Obj* CONST objv[])); +static int ItclOldBiPreviousCmd _ANSI_ARGS_((ClientData cdata, + Tcl_Interp *interp, int objc, Tcl_Obj* CONST objv[])); + +static int ItclOldBiInfoMethodsCmd _ANSI_ARGS_((ClientData cdata, + Tcl_Interp *interp, int objc, Tcl_Obj* CONST objv[])); +static int ItclOldBiInfoProcsCmd _ANSI_ARGS_((ClientData cdata, + Tcl_Interp *interp, int objc, Tcl_Obj* CONST objv[])); +static int ItclOldBiInfoPublicsCmd _ANSI_ARGS_((ClientData cdata, + Tcl_Interp *interp, int objc, Tcl_Obj* CONST objv[])); +static int ItclOldBiInfoProtectedsCmd _ANSI_ARGS_((ClientData cdata, + Tcl_Interp *interp, int objc, Tcl_Obj* CONST objv[])); +static int ItclOldBiInfoCommonsCmd _ANSI_ARGS_((ClientData cdata, + Tcl_Interp *interp, int objc, Tcl_Obj* CONST objv[])); + + +/* + * Standard list of built-in methods for old-style objects. + */ +typedef struct BiMethod { + char* name; /* method name */ + char* usage; /* string describing usage */ + char* registration; /* registration name for C proc */ + Tcl_ObjCmdProc *proc; /* implementation C proc */ +} BiMethod; + +static BiMethod BiMethodList[] = { + { "cget", "-option", + "@itcl-oldstyle-cget", Itcl_BiCgetCmd }, + { "configure", "?-option? ?value -option value...?", + "@itcl-oldstyle-configure", Itcl_BiConfigureCmd }, + { "delete", "", + "@itcl-oldstyle-delete", ItclOldBiDeleteCmd }, + { "isa", "className", + "@itcl-oldstyle-isa", Itcl_BiIsaCmd }, +}; +static int BiMethodListLen = sizeof(BiMethodList)/sizeof(BiMethod); + + +/* + * ------------------------------------------------------------------------ + * Itcl_OldInit() + * + * Invoked by Itcl_Init() whenever a new interpeter is created to add + * [incr Tcl] facilities. Adds the commands needed for backward + * compatibility with previous releases of [incr Tcl]. + * ------------------------------------------------------------------------ + */ +int +Itcl_OldInit(interp,info) + Tcl_Interp *interp; /* interpreter to be updated */ + ItclObjectInfo *info; /* info regarding all known objects */ +{ + int i; + Tcl_Namespace *parserNs, *oldBiNs; + + /* + * Declare all of the old-style built-in methods as C procedures. + */ + for (i=0; i < BiMethodListLen; i++) { + if (Itcl_RegisterObjC(interp, + BiMethodList[i].registration+1, BiMethodList[i].proc, + (ClientData)NULL, (Tcl_CmdDeleteProc*)NULL) != TCL_OK) { + + return TCL_ERROR; + } + } + + /* + * Create the "itcl::old-parser" namespace for backward + * compatibility, to handle the old-style class definitions. + */ + parserNs = Tcl_CreateNamespace(interp, "::itcl::old-parser", + (ClientData)info, Itcl_ReleaseData); + + if (!parserNs) { + Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), + " (cannot initialize itcl old-style parser)", + (char*)NULL); + return TCL_ERROR; + } + Itcl_PreserveData((ClientData)info); + + /* + * Add commands for parsing old-style class definitions. + */ + Tcl_CreateObjCommand(interp, "::itcl::old-parser::inherit", + Itcl_ClassInheritCmd, (ClientData)info, (Tcl_CmdDeleteProc*)NULL); + + Tcl_CreateObjCommand(interp, "::itcl::old-parser::constructor", + Itcl_ClassConstructorCmd, (ClientData)info, (Tcl_CmdDeleteProc*)NULL); + + Tcl_CreateObjCommand(interp, "::itcl::old-parser::destructor", + Itcl_ClassDestructorCmd, (ClientData)info, (Tcl_CmdDeleteProc*)NULL); + + Tcl_CreateObjCommand(interp, "::itcl::old-parser::method", + ItclOldMethodCmd, (ClientData)info, (Tcl_CmdDeleteProc*)NULL); + + Tcl_CreateObjCommand(interp, "::itcl::old-parser::proc", + Itcl_ClassProcCmd, (ClientData)info, (Tcl_CmdDeleteProc*)NULL); + + Tcl_CreateObjCommand(interp, "::itcl::old-parser::public", + ItclOldPublicCmd, (ClientData)info, (Tcl_CmdDeleteProc*)NULL); + + Tcl_CreateObjCommand(interp, "::itcl::old-parser::protected", + ItclOldProtectedCmd, (ClientData)info, (Tcl_CmdDeleteProc*)NULL); + + Tcl_CreateObjCommand(interp, "::itcl::old-parser::common", + ItclOldCommonCmd, (ClientData)info, (Tcl_CmdDeleteProc*)NULL); + + /* + * Set the runtime variable resolver for the parser namespace, + * to control access to "common" data members while parsing + * the class definition. + */ + Tcl_SetNamespaceResolvers(parserNs, (Tcl_ResolveCmdProc*)NULL, + Itcl_ParseVarResolver, (Tcl_ResolveCompiledVarProc*)NULL); + + /* + * Create the "itcl::old-builtin" namespace for backward + * compatibility with the old-style built-in commands. + */ + Tcl_CreateObjCommand(interp, "::itcl::old-builtin::virtual", + ItclOldBiVirtualCmd, (ClientData)NULL, (Tcl_CmdDeleteProc*)NULL); + + Tcl_CreateObjCommand(interp, "::itcl::old-builtin::previous", + ItclOldBiPreviousCmd, (ClientData)NULL, (Tcl_CmdDeleteProc*)NULL); + + if (Itcl_CreateEnsemble(interp, "::itcl::old-builtin::info") != TCL_OK) { + return TCL_ERROR; + } + + if (Itcl_AddEnsemblePart(interp, "::itcl::old-builtin::info", + "class", "", Itcl_BiInfoClassCmd, + (ClientData)NULL, (Tcl_CmdDeleteProc*)NULL) + != TCL_OK || + + Itcl_AddEnsemblePart(interp, "::itcl::old-builtin::info", + "inherit", "", Itcl_BiInfoInheritCmd, + (ClientData)NULL, (Tcl_CmdDeleteProc*)NULL) + != TCL_OK || + + Itcl_AddEnsemblePart(interp, "::itcl::old-builtin::info", + "heritage", "", Itcl_BiInfoHeritageCmd, + (ClientData)NULL, (Tcl_CmdDeleteProc*)NULL) + != TCL_OK || + + Itcl_AddEnsemblePart(interp, "::itcl::old-builtin::info", + "method", "?methodName? ?-args? ?-body?", + ItclOldBiInfoMethodsCmd, + (ClientData)NULL, (Tcl_CmdDeleteProc*)NULL) + != TCL_OK || + + Itcl_AddEnsemblePart(interp, "::itcl::old-builtin::info", + "proc", "?procName? ?-args? ?-body?", + ItclOldBiInfoProcsCmd, + (ClientData)NULL, (Tcl_CmdDeleteProc*)NULL) + != TCL_OK || + + Itcl_AddEnsemblePart(interp, "::itcl::old-builtin::info", + "public", "?varName? ?-init? ?-value? ?-config?", + ItclOldBiInfoPublicsCmd, + (ClientData)NULL, (Tcl_CmdDeleteProc*)NULL) + != TCL_OK || + + Itcl_AddEnsemblePart(interp, "::itcl::old-builtin::info", + "protected", "?varName? ?-init? ?-value?", + ItclOldBiInfoProtectedsCmd, + (ClientData)NULL,(Tcl_CmdDeleteProc*)NULL) + != TCL_OK || + + Itcl_AddEnsemblePart(interp, "::itcl::old-builtin::info", + "common", "?varName? ?-init? ?-value?", + ItclOldBiInfoCommonsCmd, + (ClientData)NULL,(Tcl_CmdDeleteProc*)NULL) + != TCL_OK || + + Itcl_AddEnsemblePart(interp, "::itcl::old-builtin::info", + "args", "procname", Itcl_BiInfoArgsCmd, + (ClientData)NULL, (Tcl_CmdDeleteProc*)NULL) + != TCL_OK || + + Itcl_AddEnsemblePart(interp, "::itcl::old-builtin::info", + "body", "procname", Itcl_BiInfoBodyCmd, + (ClientData)NULL, (Tcl_CmdDeleteProc*)NULL) + != TCL_OK + ) { + return TCL_ERROR; + } + + /* + * Plug in an "@error" handler to handle other options from + * the usual info command. + */ + if (Itcl_AddEnsemblePart(interp, "::itcl::old-builtin::info", + "@error", (char*)NULL, Itcl_DefaultInfoCmd, + (ClientData)NULL, (Tcl_CmdDeleteProc*)NULL) + != TCL_OK + ) { + return TCL_ERROR; + } + + oldBiNs = Tcl_FindNamespace(interp, "::itcl::old-builtin", + (Tcl_Namespace*)NULL, TCL_LEAVE_ERR_MSG); + + if (!oldBiNs || + Tcl_Export(interp, oldBiNs, "*", /* resetListFirst */ 1) != TCL_OK) { + return TCL_ERROR; + } + + /* + * Install the "itcl_class" and "itcl_info" commands into + * the global scope. This supports the old syntax for + * backward compatibility. + */ + Tcl_CreateObjCommand(interp, "::itcl_class", ItclOldClassCmd, + (ClientData)info, Itcl_ReleaseData); + Itcl_PreserveData((ClientData)info); + + + if (Itcl_CreateEnsemble(interp, "::itcl_info") != TCL_OK) { + return TCL_ERROR; + } + + if (Itcl_AddEnsemblePart(interp, "::itcl_info", + "classes", "?pattern?", + Itcl_FindClassesCmd, (ClientData)info, Itcl_ReleaseData) + != TCL_OK) { + return TCL_ERROR; + } + Itcl_PreserveData((ClientData)info); + + if (Itcl_AddEnsemblePart(interp, "::itcl_info", + "objects", "?-class className? ?-isa className? ?pattern?", + Itcl_FindObjectsCmd, (ClientData)info, Itcl_ReleaseData) + != TCL_OK) { + return TCL_ERROR; + } + Itcl_PreserveData((ClientData)info); + + return TCL_OK; +} + + +/* + * ------------------------------------------------------------------------ + * Itcl_InstallOldBiMethods() + * + * Invoked when a class is first created, just after the class + * definition has been parsed, to add definitions for built-in + * methods to the class. If a method already exists in the class + * with the same name as the built-in, then the built-in is skipped. + * Otherwise, a method definition for the built-in method is added. + * + * Returns TCL_OK if successful, or TCL_ERROR (along with an error + * message in the interpreter) if anything goes wrong. + * ------------------------------------------------------------------------ + */ +int +Itcl_InstallOldBiMethods(interp, cdefn) + Tcl_Interp *interp; /* current interpreter */ + ItclClass *cdefn; /* class definition to be updated */ +{ + int result = TCL_OK; + + int i; + ItclHierIter hier; + ItclClass *cdPtr; + Tcl_HashEntry *entry; + + /* + * Scan through all of the built-in methods and see if + * that method already exists in the class. If not, add + * it in. + * + * TRICKY NOTE: The virtual tables haven't been built yet, + * so look for existing methods the hard way--by scanning + * through all classes. + */ + for (i=0; i < BiMethodListLen; i++) { + Itcl_InitHierIter(&hier, cdefn); + cdPtr = Itcl_AdvanceHierIter(&hier); + + entry = NULL; + while (cdPtr) { + entry = Tcl_FindHashEntry(&cdPtr->functions, BiMethodList[i].name); + if (entry) { + break; + } + cdPtr = Itcl_AdvanceHierIter(&hier); + } + Itcl_DeleteHierIter(&hier); + + if (!entry) { + result = Itcl_CreateMethod(interp, cdefn, BiMethodList[i].name, + BiMethodList[i].usage, BiMethodList[i].registration); + + if (result != TCL_OK) { + break; + } + } + } + return result; +} + + +/* + * ------------------------------------------------------------------------ + * ItclOldClassCmd() + * + * Invoked by Tcl whenever the user issues a "itcl_class" command to + * specify a class definition. Handles the following syntax: + * + * itcl_class { + * inherit ... + * + * constructor {} { } + * destructor { } + * + * method {} { } + * proc {} { } + * + * public ?? ?? + * protected ?? + * common ?? + * } + * + * NOTE: This command is will only be provided for a limited time, + * to support backward compatibility with the old-style + * [incr Tcl] syntax. Users should convert their scripts + * to use the newer syntax (Itcl_ClassCmd()) as soon as possible. + * + * ------------------------------------------------------------------------ + */ +static int +ItclOldClassCmd(clientData, interp, objc, objv) + ClientData clientData; /* info for all known objects */ + Tcl_Interp *interp; /* current interpreter */ + int objc; /* number of arguments */ + Tcl_Obj *CONST objv[]; /* argument objects */ +{ + ItclObjectInfo* info = (ItclObjectInfo*)clientData; + + int result; + char *className; + Tcl_Namespace *parserNs; + ItclClass *cdefnPtr; + Tcl_HashEntry* entry; + ItclMemberFunc *mfunc; + Tcl_CallFrame frame; + + if (objc != 3) { + Tcl_WrongNumArgs(interp, 1, objv, "name { definition }"); + return TCL_ERROR; + } + className = Tcl_GetStringFromObj(objv[1], (int*)NULL); + + /* + * Find the namespace to use as a parser for the class definition. + * If for some reason it is destroyed, bail out here. + */ + parserNs = Tcl_FindNamespace(interp, "::itcl::old-parser", + (Tcl_Namespace*)NULL, TCL_LEAVE_ERR_MSG); + + if (parserNs == NULL) { + char msg[256]; + sprintf(msg, "\n (while parsing class definition for \"%.100s\")", + className); + Tcl_AddErrorInfo(interp, msg); + return TCL_ERROR; + } + + /* + * Try to create the specified class and its namespace. + */ + if (Itcl_CreateClass(interp, className, info, &cdefnPtr) != TCL_OK) { + return TCL_ERROR; + } + cdefnPtr->flags |= ITCL_OLD_STYLE; + + /* + * Import the built-in commands from the itcl::old-builtin + * and itcl::builtin namespaces. Do this before parsing the + * class definition, so methods/procs can override the built-in + * commands. + */ + result = Tcl_Import(interp, cdefnPtr->namesp, "::itcl::builtin::*", + /* allowOverwrite */ 1); + + if (result == TCL_OK) { + result = Tcl_Import(interp, cdefnPtr->namesp, "::itcl::old-builtin::*", + /* allowOverwrite */ 1); + } + + if (result != TCL_OK) { + char msg[256]; + sprintf(msg, "\n (while installing built-in commands for class \"%.100s\")", className); + Tcl_AddErrorInfo(interp, msg); + + Tcl_DeleteNamespace(cdefnPtr->namesp); + return TCL_ERROR; + } + + /* + * Push this class onto the class definition stack so that it + * becomes the current context for all commands in the parser. + * Activate the parser and evaluate the class definition. + */ + Itcl_PushStack((ClientData)cdefnPtr, &info->cdefnStack); + + result = Tcl_PushCallFrame(interp, &frame, parserNs, + /* isProcCallFrame */ 0); + + if (result == TCL_OK) { + /* CYGNUS LOCAL - Fix for Tcl8.1 */ +#if TCL_MAJOR_VERSION == 8 && TCL_MINOR_VERSION == 0 + result = Tcl_EvalObj(interp, objv[2]); +#else + result = Tcl_EvalObj(interp, objv[2], 0); +#endif + /* END CYGNUS LOCAL */ + Tcl_PopCallFrame(interp); + } + Itcl_PopStack(&info->cdefnStack); + + if (result != TCL_OK) { + char msg[256]; + sprintf(msg, "\n (class \"%.200s\" body line %d)", + className, interp->errorLine); + Tcl_AddErrorInfo(interp, msg); + + Tcl_DeleteNamespace(cdefnPtr->namesp); + return TCL_ERROR; + } + + /* + * At this point, parsing of the class definition has succeeded. + * Add built-in methods such as "configure" and "cget"--as long + * as they don't conflict with those defined in the class. + */ + if (Itcl_InstallOldBiMethods(interp, cdefnPtr) != TCL_OK) { + Tcl_DeleteNamespace(cdefnPtr->namesp); + return TCL_ERROR; + } + + /* + * See if this class has a "constructor", and if it does, mark + * it as "old-style". This will allow the "config" argument + * to work. + */ + entry = Tcl_FindHashEntry(&cdefnPtr->functions, "constructor"); + if (entry) { + mfunc = (ItclMemberFunc*)Tcl_GetHashValue(entry); + mfunc->member->flags |= ITCL_OLD_STYLE; + } + + /* + * Build the virtual tables for this class. + */ + Itcl_BuildVirtualTables(cdefnPtr); + + Tcl_ResetResult(interp); + return TCL_OK; +} + + +/* + * ------------------------------------------------------------------------ + * ItclOldMethodCmd() + * + * Invoked by Tcl during the parsing of a class definition whenever + * the "method" command is invoked to define an object method. + * Handles the following syntax: + * + * method {} {} + * + * ------------------------------------------------------------------------ + */ +static int +ItclOldMethodCmd(clientData, interp, objc, objv) + ClientData clientData; /* info for all known objects */ + Tcl_Interp *interp; /* current interpreter */ + int objc; /* number of arguments */ + Tcl_Obj *CONST objv[]; /* argument objects */ +{ + ItclObjectInfo *info = (ItclObjectInfo*)clientData; + ItclClass *cdefn = (ItclClass*)Itcl_PeekStack(&info->cdefnStack); + + char *name, *arglist, *body; + Tcl_HashEntry *entry; + ItclMemberFunc *mfunc; + + if (objc != 4) { + Tcl_WrongNumArgs(interp, 1, objv, "name args body"); + return TCL_ERROR; + } + + name = Tcl_GetStringFromObj(objv[1], (int*)NULL); + if (Tcl_FindHashEntry(&cdefn->functions, name)) { + Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), + "\"", name, "\" already defined in class \"", cdefn->name, "\"", + (char*)NULL); + return TCL_ERROR; + } + + arglist = Tcl_GetStringFromObj(objv[2], (int*)NULL); + body = Tcl_GetStringFromObj(objv[3], (int*)NULL); + + if (Itcl_CreateMethod(interp, cdefn, name, arglist, body) != TCL_OK) { + return TCL_ERROR; + } + + /* + * Find the method that was just created and mark it as an + * "old-style" method, so that the magic "config" argument + * will be allowed to work. This is done for backward- + * compatibility with earlier releases. In the latest version, + * use of the "config" argument is discouraged. + */ + entry = Tcl_FindHashEntry(&cdefn->functions, name); + if (entry) { + mfunc = (ItclMemberFunc*)Tcl_GetHashValue(entry); + mfunc->member->flags |= ITCL_OLD_STYLE; + } + + return TCL_OK; +} + + +/* + * ------------------------------------------------------------------------ + * ItclOldPublicCmd() + * + * Invoked by Tcl during the parsing of a class definition whenever + * the "public" command is invoked to define a public variable. + * Handles the following syntax: + * + * public ?? ?? + * + * ------------------------------------------------------------------------ + */ +static int +ItclOldPublicCmd(clientData, interp, objc, objv) + ClientData clientData; /* info for all known objects */ + Tcl_Interp *interp; /* current interpreter */ + int objc; /* number of arguments */ + Tcl_Obj *CONST objv[]; /* argument objects */ +{ + ItclObjectInfo *info = (ItclObjectInfo*)clientData; + ItclClass *cdefnPtr = (ItclClass*)Itcl_PeekStack(&info->cdefnStack); + + char *name, *init, *config; + ItclVarDefn *vdefn; + + if ((objc < 2) || (objc > 4)) { + Tcl_WrongNumArgs(interp, 1, objv, "varname ?init? ?config?"); + return TCL_ERROR; + } + + /* + * Make sure that the variable name does not contain anything + * goofy like a "::" scope qualifier. + */ + name = Tcl_GetStringFromObj(objv[1], (int*)NULL); + if (strstr(name, "::")) { + Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), + "bad variable name \"", name, "\"", + (char*)NULL); + return TCL_ERROR; + } + + init = NULL; + config = NULL; + if (objc >= 3) { + init = Tcl_GetStringFromObj(objv[2], (int*)NULL); + } + if (objc >= 4) { + config = Tcl_GetStringFromObj(objv[3], (int*)NULL); + } + + if (Itcl_CreateVarDefn(interp, cdefnPtr, name, init, config, + &vdefn) != TCL_OK) { + + return TCL_ERROR; + } + + vdefn->member->protection = ITCL_PUBLIC; + + return TCL_OK; +} + +/* + * ------------------------------------------------------------------------ + * ItclOldProtectedCmd() + * + * Invoked by Tcl during the parsing of a class definition whenever + * the "protected" command is invoked to define a protected variable. + * Handles the following syntax: + * + * protected ?? + * + * ------------------------------------------------------------------------ + */ +static int +ItclOldProtectedCmd(clientData, interp, objc, objv) + ClientData clientData; /* info for all known objects */ + Tcl_Interp *interp; /* current interpreter */ + int objc; /* number of arguments */ + Tcl_Obj *CONST objv[]; /* argument objects */ +{ + ItclObjectInfo *info = (ItclObjectInfo*)clientData; + ItclClass *cdefnPtr = (ItclClass*)Itcl_PeekStack(&info->cdefnStack); + + char *name, *init; + ItclVarDefn *vdefn; + + if ((objc < 2) || (objc > 3)) { + Tcl_WrongNumArgs(interp, 1, objv, "varname ?init?"); + return TCL_ERROR; + } + + /* + * Make sure that the variable name does not contain anything + * goofy like a "::" scope qualifier. + */ + name = Tcl_GetStringFromObj(objv[1], (int*)NULL); + if (strstr(name, "::")) { + Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), + "bad variable name \"", name, "\"", + (char*)NULL); + return TCL_ERROR; + } + + if (objc == 3) { + init = Tcl_GetStringFromObj(objv[2], (int*)NULL); + } else { + init = NULL; + } + + if (Itcl_CreateVarDefn(interp, cdefnPtr, name, init, (char*)NULL, + &vdefn) != TCL_OK) { + + return TCL_ERROR; + } + + vdefn->member->protection = ITCL_PROTECTED; + + return TCL_OK; +} + +/* + * ------------------------------------------------------------------------ + * ItclOldCommonCmd() + * + * Invoked by Tcl during the parsing of a class definition whenever + * the "common" command is invoked to define a variable that is + * common to all objects in the class. Handles the following syntax: + * + * common ?? + * + * ------------------------------------------------------------------------ + */ +static int +ItclOldCommonCmd(clientData, interp, objc, objv) + ClientData clientData; /* info for all known objects */ + Tcl_Interp *interp; /* current interpreter */ + int objc; /* number of arguments */ + Tcl_Obj *CONST objv[]; /* argument objects */ +{ + ItclObjectInfo *info = (ItclObjectInfo*)clientData; + ItclClass *cdefnPtr = (ItclClass*)Itcl_PeekStack(&info->cdefnStack); + + int newEntry; + char *name, *init; + ItclVarDefn *vdefn; + Tcl_HashEntry *entry; + Namespace *nsPtr; + Var *varPtr; + + if ((objc < 2) || (objc > 3)) { + Tcl_WrongNumArgs(interp, 1, objv, "varname ?init?"); + return TCL_ERROR; + } + + /* + * Make sure that the variable name does not contain anything + * goofy like a "::" scope qualifier. + */ + name = Tcl_GetStringFromObj(objv[1], (int*)NULL); + if (strstr(name, "::")) { + Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), + "bad variable name \"", name, "\"", + (char*)NULL); + return TCL_ERROR; + } + + if (objc == 3) { + init = Tcl_GetStringFromObj(objv[2], (int*)NULL); + } else { + init = NULL; + } + + if (Itcl_CreateVarDefn(interp, cdefnPtr, name, init, (char*)NULL, + &vdefn) != TCL_OK) { + + return TCL_ERROR; + } + + vdefn->member->protection = ITCL_PROTECTED; + vdefn->member->flags |= ITCL_COMMON; + + /* + * Create the variable in the namespace associated with the + * class. Do this the hard way, to avoid the variable resolver + * procedures. These procedures won't work until we rebuild + * the virtual tables below. + */ + nsPtr = (Namespace*)cdefnPtr->namesp; + entry = Tcl_CreateHashEntry(&nsPtr->varTable, + vdefn->member->name, &newEntry); + + varPtr = _TclNewVar(); + varPtr->hPtr = entry; + varPtr->nsPtr = nsPtr; + varPtr->refCount++; /* protect from being deleted */ + + Tcl_SetHashValue(entry, varPtr); + + /* + * TRICKY NOTE: Make sure to rebuild the virtual tables for this + * class so that this variable is ready to access. The variable + * resolver for the parser namespace needs this info to find the + * variable if the developer tries to set it within the class + * definition. + * + * If an initialization value was specified, then initialize + * the variable now. + */ + Itcl_BuildVirtualTables(cdefnPtr); + + if (init) { + init = Tcl_SetVar(interp, vdefn->member->name, init, + TCL_NAMESPACE_ONLY); + + if (!init) { + Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), + "cannot initialize common variable \"", + vdefn->member->name, "\"", + (char*)NULL); + return TCL_ERROR; + } + } + return TCL_OK; +} + + +/* + * ------------------------------------------------------------------------ + * ItclOldDeleteCmd() + * + * Invokes the destructors, and deletes the object that invoked this + * operation. If an error is encountered during destruction, the + * delete operation is aborted. Handles the following syntax: + * + * delete + * + * When an object is successfully deleted, it is removed from the + * list of known objects, and its access command is deleted. + * ------------------------------------------------------------------------ + */ +/* ARGSUSED */ +static int +ItclOldBiDeleteCmd(dummy, interp, objc, objv) + ClientData dummy; /* not used */ + Tcl_Interp *interp; /* current interpreter */ + int objc; /* number of arguments */ + Tcl_Obj *CONST objv[]; /* argument objects */ +{ + ItclClass *contextClass; + ItclObject *contextObj; + + if (objc != 1) { + Tcl_WrongNumArgs(interp, 1, objv, ""); + return TCL_ERROR; + } + + /* + * If there is an object context, then destruct the object + * and delete it. + */ + if (Itcl_GetContext(interp, &contextClass, &contextObj) != TCL_OK) { + return TCL_ERROR; + } + + if (!contextObj) { + Tcl_SetResult(interp, "improper usage: should be \"object delete\"", + TCL_STATIC); + return TCL_ERROR; + } + + if (Itcl_DeleteObject(interp, contextObj) != TCL_OK) { + return TCL_ERROR; + } + + Tcl_ResetResult(interp); + return TCL_OK; +} + + +/* + * ------------------------------------------------------------------------ + * ItclOldVirtualCmd() + * + * Executes the remainder of its command line arguments in the + * most-specific class scope for the current object. If there is + * no object context, this fails. + * + * NOTE: All methods are now implicitly virtual, and there are + * much better ways to manipulate scope. This command is only + * provided for backward-compatibility, and should be avoided. + * + * Returns a status TCL_OK/TCL_ERROR to indicate success/failure. + * ------------------------------------------------------------------------ + */ +/* ARGSUSED */ +static int +ItclOldBiVirtualCmd(dummy, interp, objc, objv) + ClientData dummy; /* not used */ + Tcl_Interp *interp; /* current interpreter */ + int objc; /* number of arguments */ + Tcl_Obj *CONST objv[]; /* argument objects */ +{ + int result; + ItclClass *contextClass; + ItclObject *contextObj; + ItclContext context; + + if (objc == 1) { + Tcl_WrongNumArgs(interp, 1, objv, "command ?args...?"); + Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), + "\n This command will be removed soon.", + "\n Commands are now virtual by default.", + (char*)NULL); + return TCL_ERROR; + } + + /* + * If there is no object context, then return an error. + */ + if (Itcl_GetContext(interp, &contextClass, &contextObj) != TCL_OK) { + return TCL_ERROR; + } + if (!contextObj) { + Tcl_ResetResult(interp); + Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), + "cannot use \"virtual\" without an object context\n", + " This command will be removed soon.\n", + " Commands are now virtual by default.", + (char*)NULL); + return TCL_ERROR; + } + + /* + * Install the most-specific namespace for this object, with + * the object context as clientData. Invoke the rest of the + * args as a command in that namespace. + */ + if (Itcl_PushContext(interp, (ItclMember*)NULL, contextObj->classDefn, + contextObj, &context) != TCL_OK) { + + return TCL_ERROR; + } + + result = Itcl_EvalArgs(interp, objc-1, objv+1); + Itcl_PopContext(interp, &context); + + return result; +} + + +/* + * ------------------------------------------------------------------------ + * ItclOldPreviousCmd() + * + * Executes the remainder of its command line arguments in the + * previous class scope (i.e., the next scope up in the heritage + * list). + * + * NOTE: There are much better ways to manipulate scope. This + * command is only provided for backward-compatibility, and should + * be avoided. + * + * Returns a status TCL_OK/TCL_ERROR to indicate success/failure. + * ------------------------------------------------------------------------ + */ +/* ARGSUSED */ +static int +ItclOldBiPreviousCmd(dummy, interp, objc, objv) + ClientData dummy; /* not used */ + Tcl_Interp *interp; /* current interpreter */ + int objc; /* number of arguments */ + Tcl_Obj *CONST objv[]; /* argument objects */ +{ + int result; + char *name; + ItclClass *contextClass, *base; + ItclObject *contextObj; + ItclMember *member; + ItclMemberFunc *mfunc; + Itcl_ListElem *elem; + Tcl_HashEntry *entry; + + if (objc < 2) { + Tcl_WrongNumArgs(interp, 1, objv, "command ?args...?"); + return TCL_ERROR; + } + + /* + * If the current context is not a class namespace, + * return an error. + */ + if (Itcl_GetContext(interp, &contextClass, &contextObj) != TCL_OK) { + return TCL_ERROR; + } + + /* + * Get the heritage information for this class and move one + * level up in the hierarchy. If there is no base class, + * return an error. + */ + elem = Itcl_FirstListElem(&contextClass->bases); + if (!elem) { + Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), + "no previous class in inheritance hierarchy for \"", + contextClass->name, "\"", + (char*)NULL); + return TCL_ERROR; + } + base = (ItclClass*)Itcl_GetListValue(elem); + + /* + * Look in the command resolution table for the base class + * to find the desired method. + */ + name = Tcl_GetStringFromObj(objv[1], (int*)NULL); + entry = Tcl_FindHashEntry(&base->resolveCmds, name); + if (!entry) { + Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), + "invalid command name \"", base->name, "::", name, "\"", + (char*)NULL); + return TCL_ERROR; + } + + mfunc = (ItclMemberFunc*)Tcl_GetHashValue(entry); + member = mfunc->member; + + /* + * Make sure that this method is accessible. + */ + if (mfunc->member->protection != ITCL_PUBLIC) { + Tcl_Namespace *contextNs = Itcl_GetTrueNamespace(interp, + member->classDefn->info); + + if (!Itcl_CanAccessFunc(mfunc, contextNs)) { + Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), + "can't access \"", member->fullname, "\": ", + Itcl_ProtectionStr(member->protection), " function", + (char*)NULL); + return TCL_ERROR; + } + } + + /* + * Invoke the desired method by calling Itcl_EvalMemberCode. + * directly. This bypasses the virtual behavior built into + * the usual Itcl_ExecMethod handler. + */ + result = Itcl_EvalMemberCode(interp, mfunc, member, contextObj, + objc-1, objv+1); + + result = Itcl_ReportFuncErrors(interp, mfunc, contextObj, result); + + return result; +} + + +/* + * ------------------------------------------------------------------------ + * ItclOldBiInfoMethodsCmd() + * + * Returns information regarding methods for an object. This command + * can be invoked with or without an object context: + * + * info... <= returns info for most-specific class + * info... <= returns info for active namespace + * + * Handles the following syntax: + * + * info method ?methodName? ?-args? ?-body? + * + * If the ?methodName? is not specified, then a list of all known + * methods is returned. Otherwise, the information (args/body) for + * a specific method is returned. Returns a status TCL_OK/TCL_ERROR + * to indicate success/failure. + * ------------------------------------------------------------------------ + */ +/* ARGSUSED */ +static int +ItclOldBiInfoMethodsCmd(dummy, interp, objc, objv) + ClientData dummy; /* not used */ + Tcl_Interp *interp; /* current interpreter */ + int objc; /* number of arguments */ + Tcl_Obj *CONST objv[]; /* argument objects */ +{ + char *methodName = NULL; + int methodArgs = 0; + int methodBody = 0; + + char *token; + ItclClass *contextClass, *cdefn; + ItclObject *contextObj; + ItclHierIter hier; + Tcl_HashSearch place; + Tcl_HashEntry *entry; + ItclMemberFunc *mfunc; + ItclMemberCode *mcode; + Tcl_Obj *objPtr, *listPtr; + + /* + * If this command is not invoked within a class namespace, + * signal an error. + */ + if (Itcl_GetContext(interp, &contextClass, &contextObj) != TCL_OK) { + return TCL_ERROR; + } + + /* + * If there is an object context, then use the most-specific + * class for the object. Otherwise, use the current class + * namespace. + */ + if (contextObj) { + contextClass = contextObj->classDefn; + } + + /* + * Process args: ?methodName? ?-args? ?-body? + */ + objv++; /* skip over command name */ + objc--; + + if (objc > 0) { + methodName = Tcl_GetStringFromObj(*objv, (int*)NULL); + objc--; objv++; + } + for ( ; objc > 0; objc--, objv++) { + token = Tcl_GetStringFromObj(*objv, (int*)NULL); + if (strcmp(token, "-args") == 0) + methodArgs = ~0; + else if (strcmp(token, "-body") == 0) + methodBody = ~0; + else { + Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), + "bad option \"", token, "\": should be -args or -body", + (char*)NULL); + return TCL_ERROR; + } + } + + /* + * Return info for a specific method. + */ + if (methodName) { + entry = Tcl_FindHashEntry(&contextClass->resolveCmds, methodName); + if (entry) { + int i, valc = 0; + Tcl_Obj *valv[5]; + + mfunc = (ItclMemberFunc*)Tcl_GetHashValue(entry); + if ((mfunc->member->flags & ITCL_COMMON) != 0) { + return TCL_OK; + } + + /* + * If the implementation has not yet been defined, + * autoload it now. + */ + if (Itcl_GetMemberCode(interp, mfunc->member) != TCL_OK) { + return TCL_ERROR; + } + mcode = mfunc->member->code; + + if (!methodArgs && !methodBody) { + objPtr = Tcl_NewStringObj(mfunc->member->classDefn->name, -1); + Tcl_AppendToObj(objPtr, "::", -1); + Tcl_AppendToObj(objPtr, mfunc->member->name, -1); + Tcl_IncrRefCount(objPtr); + valv[valc++] = objPtr; + methodArgs = methodBody = ~0; + } + if (methodArgs) { + if (mcode->arglist) { + objPtr = Itcl_ArgList(mcode->argcount, mcode->arglist); + Tcl_IncrRefCount(objPtr); + valv[valc++] = objPtr; + } + else { + objPtr = Tcl_NewStringObj("", -1); + Tcl_IncrRefCount(objPtr); + valv[valc++] = objPtr; + } + } + if (methodBody) { + objPtr = mcode->procPtr->bodyPtr; + Tcl_IncrRefCount(objPtr); + valv[valc++] = objPtr; + } + + /* + * If the result list has a single element, then + * return it using Tcl_SetResult() so that it will + * look like a string and not a list with one element. + */ + if (valc == 1) { + objPtr = valv[0]; + } else { + objPtr = Tcl_NewListObj(valc, valv); + } + Tcl_SetObjResult(interp, objPtr); + + for (i=0; i < valc; i++) { + Tcl_DecrRefCount(valv[i]); + } + } + } + + /* + * Return the list of available methods. + */ + else { + listPtr = Tcl_NewListObj(0, (Tcl_Obj* CONST*)NULL); + + Itcl_InitHierIter(&hier, contextClass); + while ((cdefn=Itcl_AdvanceHierIter(&hier)) != NULL) { + entry = Tcl_FirstHashEntry(&cdefn->functions, &place); + while (entry) { + mfunc = (ItclMemberFunc*)Tcl_GetHashValue(entry); + + if ((mfunc->member->flags & ITCL_COMMON) == 0) { + objPtr = Tcl_NewStringObj(mfunc->member->classDefn->name, -1); + Tcl_AppendToObj(objPtr, "::", -1); + Tcl_AppendToObj(objPtr, mfunc->member->name, -1); + + Tcl_ListObjAppendElement((Tcl_Interp*)NULL, listPtr, + objPtr); + } + entry = Tcl_NextHashEntry(&place); + } + } + Itcl_DeleteHierIter(&hier); + + Tcl_SetObjResult(interp, listPtr); + } + return TCL_OK; +} + + +/* + * ------------------------------------------------------------------------ + * ItclOldBiInfoProcsCmd() + * + * Returns information regarding procs for a class. This command + * can be invoked with or without an object context: + * + * info... <= returns info for most-specific class + * info... <= returns info for active namespace + * + * Handles the following syntax: + * + * info proc ?procName? ?-args? ?-body? + * + * If the ?procName? is not specified, then a list of all known + * procs is returned. Otherwise, the information (args/body) for + * a specific proc is returned. Returns a status TCL_OK/TCL_ERROR + * to indicate success/failure. + * ------------------------------------------------------------------------ + */ +/* ARGSUSED */ +static int +ItclOldBiInfoProcsCmd(dummy, interp, objc, objv) + ClientData dummy; /* not used */ + Tcl_Interp *interp; /* current interpreter */ + int objc; /* number of arguments */ + Tcl_Obj *CONST objv[]; /* argument objects */ +{ + char *procName = NULL; + int procArgs = 0; + int procBody = 0; + + char *token; + ItclClass *contextClass, *cdefn; + ItclObject *contextObj; + ItclHierIter hier; + Tcl_HashSearch place; + Tcl_HashEntry *entry; + ItclMemberFunc *mfunc; + ItclMemberCode *mcode; + Tcl_Obj *objPtr, *listPtr; + + /* + * If this command is not invoked within a class namespace, + * signal an error. + */ + if (Itcl_GetContext(interp, &contextClass, &contextObj) != TCL_OK) { + return TCL_ERROR; + } + + /* + * If there is an object context, then use the most-specific + * class for the object. Otherwise, use the current class + * namespace. + */ + if (contextObj) { + contextClass = contextObj->classDefn; + } + + /* + * Process args: ?procName? ?-args? ?-body? + */ + objv++; /* skip over command name */ + objc--; + + if (objc > 0) { + procName = Tcl_GetStringFromObj(*objv, (int*)NULL); + objc--; objv++; + } + for ( ; objc > 0; objc--, objv++) { + token = Tcl_GetStringFromObj(*objv, (int*)NULL); + if (strcmp(token, "-args") == 0) + procArgs = ~0; + else if (strcmp(token, "-body") == 0) + procBody = ~0; + else { + Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), + "bad option \"", token, "\": should be -args or -body", + (char*)NULL); + return TCL_ERROR; + } + } + + /* + * Return info for a specific proc. + */ + if (procName) { + entry = Tcl_FindHashEntry(&contextClass->resolveCmds, procName); + if (entry) { + int i, valc = 0; + Tcl_Obj *valv[5]; + + mfunc = (ItclMemberFunc*)Tcl_GetHashValue(entry); + if ((mfunc->member->flags & ITCL_COMMON) == 0) { + return TCL_OK; + } + + /* + * If the implementation has not yet been defined, + * autoload it now. + */ + if (Itcl_GetMemberCode(interp, mfunc->member) != TCL_OK) { + return TCL_ERROR; + } + mcode = mfunc->member->code; + + if (!procArgs && !procBody) { + objPtr = Tcl_NewStringObj(mfunc->member->fullname, -1); + Tcl_IncrRefCount(objPtr); + valv[valc++] = objPtr; + procArgs = procBody = ~0; + } + if (procArgs) { + if (mcode->arglist) { + objPtr = Itcl_ArgList(mcode->argcount, mcode->arglist); + Tcl_IncrRefCount(objPtr); + valv[valc++] = objPtr; + } + else { + objPtr = Tcl_NewStringObj("", -1); + Tcl_IncrRefCount(objPtr); + valv[valc++] = objPtr; + } + } + if (procBody) { + objPtr = mcode->procPtr->bodyPtr; + Tcl_IncrRefCount(objPtr); + valv[valc++] = objPtr; + } + + /* + * If the result list has a single element, then + * return it using Tcl_SetResult() so that it will + * look like a string and not a list with one element. + */ + if (valc == 1) { + objPtr = valv[0]; + } else { + objPtr = Tcl_NewListObj(valc, valv); + } + Tcl_SetObjResult(interp, objPtr); + + for (i=0; i < valc; i++) { + Tcl_DecrRefCount(valv[i]); + } + } + } + + /* + * Return the list of available procs. + */ + else { + listPtr = Tcl_NewListObj(0, (Tcl_Obj* CONST*)NULL); + + Itcl_InitHierIter(&hier, contextClass); + while ((cdefn=Itcl_AdvanceHierIter(&hier)) != NULL) { + entry = Tcl_FirstHashEntry(&cdefn->functions, &place); + while (entry) { + mfunc = (ItclMemberFunc*)Tcl_GetHashValue(entry); + + if ((mfunc->member->flags & ITCL_COMMON) != 0) { + objPtr = Tcl_NewStringObj(mfunc->member->classDefn->name, -1); + Tcl_AppendToObj(objPtr, "::", -1); + Tcl_AppendToObj(objPtr, mfunc->member->name, -1); + + Tcl_ListObjAppendElement((Tcl_Interp*)NULL, listPtr, + objPtr); + } + entry = Tcl_NextHashEntry(&place); + } + } + Itcl_DeleteHierIter(&hier); + + Tcl_SetObjResult(interp, listPtr); + } + return TCL_OK; +} + + +/* + * ------------------------------------------------------------------------ + * ItclOldBiInfoPublicsCmd() + * + * Sets the interpreter result to contain information for public + * variables in the class. Handles the following syntax: + * + * info public ?varName? ?-init? ?-value? ?-config? + * + * If the ?varName? is not specified, then a list of all known public + * variables is returned. Otherwise, the information (init/value/config) + * for a specific variable is returned. Returns a status + * TCL_OK/TCL_ERROR to indicate success/failure. + * ------------------------------------------------------------------------ + */ +/* ARGSUSED */ +static int +ItclOldBiInfoPublicsCmd(dummy, interp, objc, objv) + ClientData dummy; /* not used */ + Tcl_Interp *interp; /* current interpreter */ + int objc; /* number of arguments */ + Tcl_Obj *CONST objv[]; /* argument objects */ +{ + char *varName = NULL; + int varInit = 0; + int varCheck = 0; + int varValue = 0; + + char *token, *val; + ItclClass *contextClass; + ItclObject *contextObj; + + ItclClass *cdPtr; + ItclVarLookup *vlookup; + ItclVarDefn *vdefn; + ItclMember *member; + ItclHierIter hier; + Tcl_HashEntry *entry; + Tcl_HashSearch place; + Tcl_Obj *objPtr, *listPtr; + + /* + * If this command is not invoked within a class namespace, + * signal an error. + */ + if (Itcl_GetContext(interp, &contextClass, &contextObj) != TCL_OK) { + return TCL_ERROR; + } + + /* + * Process args: ?varName? ?-init? ?-value? ?-config? + */ + objv++; /* skip over command name */ + objc--; + + if (objc > 0) { + varName = Tcl_GetStringFromObj(*objv, (int*)NULL); + objc--; objv++; + } + for ( ; objc > 0; objc--, objv++) { + token = Tcl_GetStringFromObj(*objv, (int*)NULL); + if (strcmp(token, "-init") == 0) + varInit = ~0; + else if (strcmp(token, "-value") == 0) + varValue = ~0; + else if (strcmp(token, "-config") == 0) + varCheck = ~0; + else { + Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), + "bad option \"", token, + "\": should be -init, -value or -config", + (char*)NULL); + return TCL_ERROR; + } + } + + /* + * Return info for a specific variable. + */ + if (varName) { + vlookup = NULL; + entry = Tcl_FindHashEntry(&contextClass->resolveVars, varName); + if (entry) { + vlookup = (ItclVarLookup*)Tcl_GetHashValue(entry); + if (vlookup->vdefn->member->protection != ITCL_PUBLIC) { + vlookup = NULL; + } + } + + if (vlookup) { + int i, valc = 0; + Tcl_Obj *valv[5]; + + member = vlookup->vdefn->member; + + if (!varInit && !varCheck && !varValue) { + objPtr = Tcl_NewStringObj(member->classDefn->name, -1); + Tcl_AppendToObj(objPtr, "::", -1); + Tcl_AppendToObj(objPtr, member->name, -1); + Tcl_IncrRefCount(objPtr); + valv[valc++] = objPtr; + varInit = varCheck = varValue = ~0; + } + if (varInit) { + val = (vlookup->vdefn->init) ? vlookup->vdefn->init : ""; + objPtr = Tcl_NewStringObj(val, -1); + Tcl_IncrRefCount(objPtr); + valv[valc++] = objPtr; + } + if (varValue) { + val = Itcl_GetInstanceVar(interp, member->fullname, + contextObj, contextObj->classDefn); + + if (!val) { + val = ""; + } + objPtr = Tcl_NewStringObj(val, -1); + Tcl_IncrRefCount(objPtr); + valv[valc++] = objPtr; + } + + if (varCheck) { + if (member->code && member->code->procPtr->bodyPtr) { + objPtr = member->code->procPtr->bodyPtr; + } else { + objPtr = Tcl_NewStringObj("", -1); + } + Tcl_IncrRefCount(objPtr); + valv[valc++] = objPtr; + } + + /* + * If the result list has a single element, then + * return it using Tcl_SetResult() so that it will + * look like a string and not a list with one element. + */ + if (valc == 1) { + objPtr = valv[0]; + } else { + objPtr = Tcl_NewListObj(valc, valv); + } + Tcl_SetObjResult(interp, objPtr); + + for (i=0; i < valc; i++) { + Tcl_DecrRefCount(valv[i]); + } + } + } + + /* + * Return the list of public variables. + */ + else { + listPtr = Tcl_NewListObj(0, (Tcl_Obj* CONST*)NULL); + + Itcl_InitHierIter(&hier, contextClass); + cdPtr = Itcl_AdvanceHierIter(&hier); + while (cdPtr != NULL) { + entry = Tcl_FirstHashEntry(&cdPtr->variables, &place); + while (entry) { + vdefn = (ItclVarDefn*)Tcl_GetHashValue(entry); + member = vdefn->member; + + if ((member->flags & ITCL_COMMON) == 0 && + member->protection == ITCL_PUBLIC) { + + objPtr = Tcl_NewStringObj(member->classDefn->name, -1); + Tcl_AppendToObj(objPtr, "::", -1); + Tcl_AppendToObj(objPtr, member->name, -1); + + Tcl_ListObjAppendElement((Tcl_Interp*)NULL, listPtr, + objPtr); + } + entry = Tcl_NextHashEntry(&place); + } + cdPtr = Itcl_AdvanceHierIter(&hier); + } + Itcl_DeleteHierIter(&hier); + + Tcl_SetObjResult(interp, listPtr); + } + return TCL_OK; +} + +/* + * ------------------------------------------------------------------------ + * ItclOldBiInfoProtectedsCmd() + * + * Sets the interpreter result to contain information for protected + * variables in the class. Handles the following syntax: + * + * info protected ?varName? ?-init? ?-value? + * + * If the ?varName? is not specified, then a list of all known public + * variables is returned. Otherwise, the information (init/value) + * for a specific variable is returned. Returns a status + * TCL_OK/TCL_ERROR to indicate success/failure. + * ------------------------------------------------------------------------ + */ +/* ARGSUSED */ +static int +ItclOldBiInfoProtectedsCmd(dummy, interp, objc, objv) + ClientData dummy; /* not used */ + Tcl_Interp *interp; /* current interpreter */ + int objc; /* number of arguments */ + Tcl_Obj *CONST objv[]; /* argument objects */ +{ + char *varName = NULL; + int varInit = 0; + int varValue = 0; + + char *token, *val; + ItclClass *contextClass; + ItclObject *contextObj; + + ItclClass *cdPtr; + ItclVarLookup *vlookup; + ItclVarDefn *vdefn; + ItclMember *member; + ItclHierIter hier; + Tcl_HashEntry *entry; + Tcl_HashSearch place; + Tcl_Obj *objPtr, *listPtr; + + /* + * If this command is not invoked within a class namespace, + * signal an error. + */ + if (Itcl_GetContext(interp, &contextClass, &contextObj) != TCL_OK) { + return TCL_ERROR; + } + + /* + * Process args: ?varName? ?-init? ?-value? + */ + objv++; /* skip over command name */ + objc--; + + if (objc > 0) { + varName = Tcl_GetStringFromObj(*objv, (int*)NULL); + objc--; objv++; + } + for ( ; objc > 0; objc--, objv++) { + token = Tcl_GetStringFromObj(*objv, (int*)NULL); + if (strcmp(token, "-init") == 0) + varInit = ~0; + else if (strcmp(token, "-value") == 0) + varValue = ~0; + else { + Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), + "bad option \"", token, "\": should be -init or -value", + (char*)NULL); + return TCL_ERROR; + } + } + + /* + * Return info for a specific variable. + */ + if (varName) { + vlookup = NULL; + entry = Tcl_FindHashEntry(&contextClass->resolveVars, varName); + if (entry) { + vlookup = (ItclVarLookup*)Tcl_GetHashValue(entry); + if (vlookup->vdefn->member->protection != ITCL_PROTECTED) { + vlookup = NULL; + } + } + + if (vlookup) { + int i, valc = 0; + Tcl_Obj *valv[5]; + + member = vlookup->vdefn->member; + + if (!varInit && !varValue) { + objPtr = Tcl_NewStringObj(member->classDefn->name, -1); + Tcl_AppendToObj(objPtr, "::", -1); + Tcl_AppendToObj(objPtr, member->name, -1); + Tcl_IncrRefCount(objPtr); + valv[valc++] = objPtr; + varInit = varValue = ~0; + } + + /* + * If this is the built-in "this" variable, then + * report the object name as its initialization string. + */ + if (varInit) { + if ((member->flags & ITCL_THIS_VAR) != 0) { + if (contextObj && contextObj->accessCmd) { + objPtr = Tcl_NewStringObj("", -1); + Tcl_IncrRefCount(objPtr); + Tcl_GetCommandFullName(contextObj->classDefn->interp, + contextObj->accessCmd, objPtr); + valv[valc++] = objPtr; + } + else { + objPtr = Tcl_NewStringObj("", -1); + Tcl_IncrRefCount(objPtr); + valv[valc++] = objPtr; + } + } + else { + val = (vlookup->vdefn->init) ? vlookup->vdefn->init : ""; + objPtr = Tcl_NewStringObj(val, -1); + Tcl_IncrRefCount(objPtr); + valv[valc++] = objPtr; + } + } + + if (varValue) { + val = Itcl_GetInstanceVar(interp, member->fullname, + contextObj, contextObj->classDefn); + + if (!val) { + val = ""; + } + objPtr = Tcl_NewStringObj(val, -1); + Tcl_IncrRefCount(objPtr); + valv[valc++] = objPtr; + } + + /* + * If the result list has a single element, then + * return it using Tcl_SetResult() so that it will + * look like a string and not a list with one element. + */ + if (valc == 1) { + objPtr = valv[0]; + } else { + objPtr = Tcl_NewListObj(valc, valv); + } + Tcl_SetObjResult(interp, objPtr); + + for (i=0; i < valc; i++) { + Tcl_DecrRefCount(valv[i]); + } + } + } + + /* + * Return the list of public variables. + */ + else { + listPtr = Tcl_NewListObj(0, (Tcl_Obj* CONST*)NULL); + + Itcl_InitHierIter(&hier, contextClass); + cdPtr = Itcl_AdvanceHierIter(&hier); + while (cdPtr != NULL) { + entry = Tcl_FirstHashEntry(&cdPtr->variables, &place); + while (entry) { + vdefn = (ItclVarDefn*)Tcl_GetHashValue(entry); + member = vdefn->member; + + if ((member->flags & ITCL_COMMON) == 0 && + member->protection == ITCL_PROTECTED) { + + objPtr = Tcl_NewStringObj(member->classDefn->name, -1); + Tcl_AppendToObj(objPtr, "::", -1); + Tcl_AppendToObj(objPtr, member->name, -1); + + Tcl_ListObjAppendElement((Tcl_Interp*)NULL, listPtr, + objPtr); + } + entry = Tcl_NextHashEntry(&place); + } + cdPtr = Itcl_AdvanceHierIter(&hier); + } + Itcl_DeleteHierIter(&hier); + + Tcl_SetObjResult(interp, listPtr); + } + return TCL_OK; +} + +/* + * ------------------------------------------------------------------------ + * ItclOldBiInfoCommonsCmd() + * + * Sets the interpreter result to contain information for common + * variables in the class. Handles the following syntax: + * + * info common ?varName? ?-init? ?-value? + * + * If the ?varName? is not specified, then a list of all known common + * variables is returned. Otherwise, the information (init/value) + * for a specific variable is returned. Returns a status + * TCL_OK/TCL_ERROR to indicate success/failure. + * ------------------------------------------------------------------------ + */ +/* ARGSUSED */ +static int +ItclOldBiInfoCommonsCmd(dummy, interp, objc, objv) + ClientData dummy; /* not used */ + Tcl_Interp *interp; /* current interpreter */ + int objc; /* number of arguments */ + Tcl_Obj *CONST objv[]; /* argument objects */ +{ + char *varName = NULL; + int varInit = 0; + int varValue = 0; + + char *token, *val; + ItclClass *contextClass; + ItclObject *contextObj; + + ItclClass *cdPtr; + ItclVarDefn *vdefn; + ItclVarLookup *vlookup; + ItclMember *member; + ItclHierIter hier; + Tcl_HashEntry *entry; + Tcl_HashSearch place; + Tcl_Obj *objPtr, *listPtr; + + /* + * If this command is not invoked within a class namespace, + * signal an error. + */ + if (Itcl_GetContext(interp, &contextClass, &contextObj) != TCL_OK) { + return TCL_ERROR; + } + + /* + * Process args: ?varName? ?-init? ?-value? + */ + objv++; /* skip over command name */ + objc--; + + if (objc > 0) { + varName = Tcl_GetStringFromObj(*objv, (int*)NULL); + objc--; objv++; + } + for ( ; objc > 0; objc--, objv++) { + token = Tcl_GetStringFromObj(*objv, (int*)NULL); + if (strcmp(token, "-init") == 0) + varInit = ~0; + else if (strcmp(token, "-value") == 0) + varValue = ~0; + else { + Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), + "bad option \"", token, "\": should be -init or -value", + (char*)NULL); + return TCL_ERROR; + } + } + + /* + * Return info for a specific variable. + */ + if (varName) { + vlookup = NULL; + entry = Tcl_FindHashEntry(&contextClass->resolveVars, varName); + if (entry) { + vlookup = (ItclVarLookup*)Tcl_GetHashValue(entry); + if (vlookup->vdefn->member->protection != ITCL_PROTECTED) { + vlookup = NULL; + } + } + + if (vlookup) { + int i, valc = 0; + Tcl_Obj *valv[5]; + + member = vlookup->vdefn->member; + + if (!varInit && !varValue) { + objPtr = Tcl_NewStringObj(member->classDefn->name, -1); + Tcl_AppendToObj(objPtr, "::", -1); + Tcl_AppendToObj(objPtr, member->name, -1); + Tcl_IncrRefCount(objPtr); + valv[valc++] = objPtr; + varInit = varValue = ~0; + } + if (varInit) { + val = (vlookup->vdefn->init) ? vlookup->vdefn->init : ""; + objPtr = Tcl_NewStringObj(val, -1); + Tcl_IncrRefCount(objPtr); + valv[valc++] = objPtr; + } + + if (varValue) { + val = Itcl_GetCommonVar(interp, member->fullname, + contextObj->classDefn); + + if (!val) { + val = ""; + } + objPtr = Tcl_NewStringObj(val, -1); + Tcl_IncrRefCount(objPtr); + valv[valc++] = objPtr; + } + + /* + * If the result list has a single element, then + * return it using Tcl_SetResult() so that it will + * look like a string and not a list with one element. + */ + if (valc == 1) { + objPtr = valv[0]; + } else { + objPtr = Tcl_NewListObj(valc, valv); + } + Tcl_SetObjResult(interp, objPtr); + + for (i=0; i < valc; i++) { + Tcl_DecrRefCount(valv[i]); + } + } + } + + /* + * Return the list of public variables. + */ + else { + listPtr = Tcl_NewListObj(0, (Tcl_Obj* CONST*)NULL); + + Itcl_InitHierIter(&hier, contextClass); + cdPtr = Itcl_AdvanceHierIter(&hier); + while (cdPtr != NULL) { + entry = Tcl_FirstHashEntry(&cdPtr->variables, &place); + while (entry) { + vdefn = (ItclVarDefn*)Tcl_GetHashValue(entry); + member = vdefn->member; + + if ((member->flags & ITCL_COMMON) && + member->protection == ITCL_PROTECTED) { + + objPtr = Tcl_NewStringObj(member->classDefn->name, -1); + Tcl_AppendToObj(objPtr, "::", -1); + Tcl_AppendToObj(objPtr, member->name, -1); + + Tcl_ListObjAppendElement((Tcl_Interp*)NULL, listPtr, + objPtr); + } + entry = Tcl_NextHashEntry(&place); + } + cdPtr = Itcl_AdvanceHierIter(&hier); + } + Itcl_DeleteHierIter(&hier); + + Tcl_SetObjResult(interp, listPtr); + } + return TCL_OK; +}
itcl_obsolete.c Property changes : Added: svn:executable ## -0,0 +1 ## +* \ No newline at end of property Index: itcl_util.c =================================================================== --- itcl_util.c (nonexistent) +++ itcl_util.c (revision 1765) @@ -0,0 +1,1383 @@ +/* + * ------------------------------------------------------------------------ + * PACKAGE: [incr Tcl] + * DESCRIPTION: Object-Oriented Extensions to Tcl + * + * [incr Tcl] provides object-oriented extensions to Tcl, much as + * C++ provides object-oriented extensions to C. It provides a means + * of encapsulating related procedures together with their shared data + * in a local namespace that is hidden from the outside world. It + * promotes code re-use through inheritance. More than anything else, + * it encourages better organization of Tcl applications through the + * object-oriented paradigm, leading to code that is easier to + * understand and maintain. + * + * This segment provides common utility functions used throughout + * the other [incr Tcl] source files. + * + * ======================================================================== + * AUTHOR: Michael J. McLennan + * Bell Labs Innovations for Lucent Technologies + * mmclennan@lucent.com + * http://www.tcltk.com/itcl + * + * RCS: $Id: itcl_util.c,v 1.1.1.1 2002-01-16 10:24:46 markom Exp $ + * ======================================================================== + * Copyright (c) 1993-1998 Lucent Technologies, Inc. + * ------------------------------------------------------------------------ + * See the file "license.terms" for information on usage and redistribution + * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + */ +#include "itclInt.h" +#include "tclCompile.h" + +/* + * POOL OF LIST ELEMENTS FOR LINKED LIST + */ +static Itcl_ListElem *listPool = NULL; +static int listPoolLen = 0; + +#define ITCL_VALID_LIST 0x01face10 /* magic bit pattern for validation */ +#define ITCL_LIST_POOL_SIZE 200 /* max number of elements in listPool */ + + +/* + * These records are used to keep track of reference-counted data + * for Itcl_PreserveData and Itcl_ReleaseData. + */ +typedef struct ItclPreservedData { + ClientData data; /* reference to data */ + int usage; /* number of active uses */ + Tcl_FreeProc *fproc; /* procedure used to free data */ +} ItclPreservedData; + +static Tcl_HashTable *ItclPreservedList = NULL; + + +/* + * This structure is used to take a snapshot of the interpreter + * state in Itcl_SaveInterpState. You can snapshot the state, + * execute a command, and then back up to the result or the + * error that was previously in progress. + */ +typedef struct InterpState { + int validate; /* validation stamp */ + int status; /* return code status */ + Tcl_Obj *objResult; /* result object */ + char *errorInfo; /* contents of errorInfo variable */ + char *errorCode; /* contents of errorCode variable */ +} InterpState; + +#define TCL_STATE_VALID 0x01233210 /* magic bit pattern for validation */ + + + +/* + * ------------------------------------------------------------------------ + * Itcl_Assert() + * + * Called whenever an assert() test fails. Prints a diagnostic + * message and abruptly exits. + * ------------------------------------------------------------------------ + */ +#ifndef NDEBUG + +void +Itcl_Assert(testExpr, fileName, lineNumber) + char *testExpr; /* string representing test expression */ + char *fileName; /* file name containing this call */ + int lineNumber; /* line number containing this call */ +{ + fprintf(stderr, "Assertion failed: \"%s\" (line %d of %s)", + testExpr, lineNumber, fileName); + abort(); +} + +#endif + + +/* + * ------------------------------------------------------------------------ + * Itcl_InitStack() + * + * Initializes a stack structure, allocating a certain amount of memory + * for the stack and setting the stack length to zero. + * ------------------------------------------------------------------------ + */ +void +Itcl_InitStack(stack) + Itcl_Stack *stack; /* stack to be initialized */ +{ + stack->values = stack->space; + stack->max = sizeof(stack->space)/sizeof(ClientData); + stack->len = 0; +} + +/* + * ------------------------------------------------------------------------ + * Itcl_DeleteStack() + * + * Destroys a stack structure, freeing any memory that may have been + * allocated to represent it. + * ------------------------------------------------------------------------ + */ +void +Itcl_DeleteStack(stack) + Itcl_Stack *stack; /* stack to be deleted */ +{ + /* + * If memory was explicitly allocated (instead of using the + * built-in buffer) then free it. + */ + if (stack->values != stack->space) { + ckfree((char*)stack->values); + } + stack->values = NULL; + stack->len = stack->max = 0; +} + +/* + * ------------------------------------------------------------------------ + * Itcl_PushStack() + * + * Pushes a piece of client data onto the top of the given stack. + * If the stack is not large enough, it is automatically resized. + * ------------------------------------------------------------------------ + */ +void +Itcl_PushStack(cdata,stack) + ClientData cdata; /* data to be pushed onto stack */ + Itcl_Stack *stack; /* stack */ +{ + ClientData *newStack; + + if (stack->len+1 >= stack->max) { + stack->max = 2*stack->max; + newStack = (ClientData*) + ckalloc((unsigned)(stack->max*sizeof(ClientData))); + + if (stack->values) { + memcpy((char*)newStack, (char*)stack->values, + (size_t)(stack->len*sizeof(ClientData))); + + if (stack->values != stack->space) + ckfree((char*)stack->values); + } + stack->values = newStack; + } + stack->values[stack->len++] = cdata; +} + +/* + * ------------------------------------------------------------------------ + * Itcl_PopStack() + * + * Pops a bit of client data from the top of the given stack. + * ------------------------------------------------------------------------ + */ +ClientData +Itcl_PopStack(stack) + Itcl_Stack *stack; /* stack to be manipulated */ +{ + if (stack->values && (stack->len > 0)) { + stack->len--; + return stack->values[stack->len]; + } + return (ClientData)NULL; +} + +/* + * ------------------------------------------------------------------------ + * Itcl_PeekStack() + * + * Gets the current value from the top of the given stack. + * ------------------------------------------------------------------------ + */ +ClientData +Itcl_PeekStack(stack) + Itcl_Stack *stack; /* stack to be examined */ +{ + if (stack->values && (stack->len > 0)) { + return stack->values[stack->len-1]; + } + return (ClientData)NULL; +} + +/* + * ------------------------------------------------------------------------ + * Itcl_GetStackValue() + * + * Gets a value at some index within the stack. Index "0" is the + * first value pushed onto the stack. + * ------------------------------------------------------------------------ + */ +ClientData +Itcl_GetStackValue(stack,pos) + Itcl_Stack *stack; /* stack to be examined */ + int pos; /* get value at this index */ +{ + if (stack->values && (stack->len > 0)) { + assert(pos < stack->len); + return stack->values[pos]; + } + return (ClientData)NULL; +} + + +/* + * ------------------------------------------------------------------------ + * Itcl_InitList() + * + * Initializes a linked list structure, setting the list to the empty + * state. + * ------------------------------------------------------------------------ + */ +void +Itcl_InitList(listPtr) + Itcl_List *listPtr; /* list to be initialized */ +{ + listPtr->validate = ITCL_VALID_LIST; + listPtr->num = 0; + listPtr->head = NULL; + listPtr->tail = NULL; +} + +/* + * ------------------------------------------------------------------------ + * Itcl_DeleteList() + * + * Destroys a linked list structure, deleting all of its elements and + * setting it to an empty state. If the elements have memory associated + * with them, this memory must be freed before deleting the list or it + * will be lost. + * ------------------------------------------------------------------------ + */ +void +Itcl_DeleteList(listPtr) + Itcl_List *listPtr; /* list to be deleted */ +{ + Itcl_ListElem *elemPtr; + + assert(listPtr->validate == ITCL_VALID_LIST); + + elemPtr = listPtr->head; + while (elemPtr) { + elemPtr = Itcl_DeleteListElem(elemPtr); + } + listPtr->validate = 0; +} + +/* + * ------------------------------------------------------------------------ + * Itcl_CreateListElem() + * + * Low-level routined used by procedures like Itcl_InsertList() and + * Itcl_AppendList() to create new list elements. If elements are + * available, one is taken from the list element pool. Otherwise, + * a new one is allocated. + * ------------------------------------------------------------------------ + */ +Itcl_ListElem* +Itcl_CreateListElem(listPtr) + Itcl_List *listPtr; /* list that will contain this new element */ +{ + Itcl_ListElem *elemPtr; + + if (listPoolLen > 0) { + elemPtr = listPool; + listPool = elemPtr->next; + --listPoolLen; + } + else { + elemPtr = (Itcl_ListElem*)ckalloc((unsigned)sizeof(Itcl_ListElem)); + } + elemPtr->owner = listPtr; + elemPtr->value = NULL; + elemPtr->next = NULL; + elemPtr->prev = NULL; + + return elemPtr; +} + +/* + * ------------------------------------------------------------------------ + * Itcl_DeleteListElem() + * + * Destroys a single element in a linked list, returning it to a pool of + * elements that can be later reused. Returns a pointer to the next + * element in the list. + * ------------------------------------------------------------------------ + */ +Itcl_ListElem* +Itcl_DeleteListElem(elemPtr) + Itcl_ListElem *elemPtr; /* list element to be deleted */ +{ + Itcl_List *listPtr; + Itcl_ListElem *nextPtr; + + nextPtr = elemPtr->next; + + if (elemPtr->prev) { + elemPtr->prev->next = elemPtr->next; + } + if (elemPtr->next) { + elemPtr->next->prev = elemPtr->prev; + } + + listPtr = elemPtr->owner; + if (elemPtr == listPtr->head) + listPtr->head = elemPtr->next; + if (elemPtr == listPtr->tail) + listPtr->tail = elemPtr->prev; + --listPtr->num; + + if (listPoolLen < ITCL_LIST_POOL_SIZE) { + elemPtr->next = listPool; + listPool = elemPtr; + ++listPoolLen; + } + else { + ckfree((char*)elemPtr); + } + return nextPtr; +} + +/* + * ------------------------------------------------------------------------ + * Itcl_InsertList() + * + * Creates a new list element containing the given value and returns + * a pointer to it. The element is inserted at the beginning of the + * specified list. + * ------------------------------------------------------------------------ + */ +Itcl_ListElem* +Itcl_InsertList(listPtr,val) + Itcl_List *listPtr; /* list being modified */ + ClientData val; /* value associated with new element */ +{ + Itcl_ListElem *elemPtr; + assert(listPtr->validate == ITCL_VALID_LIST); + + elemPtr = Itcl_CreateListElem(listPtr); + + elemPtr->value = val; + elemPtr->next = listPtr->head; + elemPtr->prev = NULL; + if (listPtr->head) { + listPtr->head->prev = elemPtr; + } + listPtr->head = elemPtr; + if (listPtr->tail == NULL) { + listPtr->tail = elemPtr; + } + ++listPtr->num; + + return elemPtr; +} + +/* + * ------------------------------------------------------------------------ + * Itcl_InsertListElem() + * + * Creates a new list element containing the given value and returns + * a pointer to it. The element is inserted in the list just before + * the specified element. + * ------------------------------------------------------------------------ + */ +Itcl_ListElem* +Itcl_InsertListElem(pos,val) + Itcl_ListElem *pos; /* insert just before this element */ + ClientData val; /* value associated with new element */ +{ + Itcl_List *listPtr; + Itcl_ListElem *elemPtr; + + listPtr = pos->owner; + assert(listPtr->validate == ITCL_VALID_LIST); + assert(pos != NULL); + + elemPtr = Itcl_CreateListElem(listPtr); + elemPtr->value = val; + + elemPtr->prev = pos->prev; + if (elemPtr->prev) { + elemPtr->prev->next = elemPtr; + } + elemPtr->next = pos; + pos->prev = elemPtr; + + if (listPtr->head == pos) { + listPtr->head = elemPtr; + } + if (listPtr->tail == NULL) { + listPtr->tail = elemPtr; + } + ++listPtr->num; + + return elemPtr; +} + +/* + * ------------------------------------------------------------------------ + * Itcl_AppendList() + * + * Creates a new list element containing the given value and returns + * a pointer to it. The element is appended at the end of the + * specified list. + * ------------------------------------------------------------------------ + */ +Itcl_ListElem* +Itcl_AppendList(listPtr,val) + Itcl_List *listPtr; /* list being modified */ + ClientData val; /* value associated with new element */ +{ + Itcl_ListElem *elemPtr; + assert(listPtr->validate == ITCL_VALID_LIST); + + elemPtr = Itcl_CreateListElem(listPtr); + + elemPtr->value = val; + elemPtr->prev = listPtr->tail; + elemPtr->next = NULL; + if (listPtr->tail) { + listPtr->tail->next = elemPtr; + } + listPtr->tail = elemPtr; + if (listPtr->head == NULL) { + listPtr->head = elemPtr; + } + ++listPtr->num; + + return elemPtr; +} + +/* + * ------------------------------------------------------------------------ + * Itcl_AppendListElem() + * + * Creates a new list element containing the given value and returns + * a pointer to it. The element is inserted in the list just after + * the specified element. + * ------------------------------------------------------------------------ + */ +Itcl_ListElem* +Itcl_AppendListElem(pos,val) + Itcl_ListElem *pos; /* insert just after this element */ + ClientData val; /* value associated with new element */ +{ + Itcl_List *listPtr; + Itcl_ListElem *elemPtr; + + listPtr = pos->owner; + assert(listPtr->validate == ITCL_VALID_LIST); + assert(pos != NULL); + + elemPtr = Itcl_CreateListElem(listPtr); + elemPtr->value = val; + + elemPtr->next = pos->next; + if (elemPtr->next) { + elemPtr->next->prev = elemPtr; + } + elemPtr->prev = pos; + pos->next = elemPtr; + + if (listPtr->tail == pos) { + listPtr->tail = elemPtr; + } + if (listPtr->head == NULL) { + listPtr->head = elemPtr; + } + ++listPtr->num; + + return elemPtr; +} + +/* + * ------------------------------------------------------------------------ + * Itcl_SetListValue() + * + * Modifies the value associated with a list element. + * ------------------------------------------------------------------------ + */ +void +Itcl_SetListValue(elemPtr,val) + Itcl_ListElem *elemPtr; /* list element being modified */ + ClientData val; /* new value associated with element */ +{ + Itcl_List *listPtr = elemPtr->owner; + assert(listPtr->validate == ITCL_VALID_LIST); + assert(elemPtr != NULL); + + elemPtr->value = val; +} + + +/* + * ======================================================================== + * REFERENCE-COUNTED DATA + * + * The following procedures manage generic reference-counted data. + * They are similar in spirit to the Tcl_Preserve/Tcl_Release + * procedures defined in the Tcl/Tk core. But these procedures use + * a hash table instead of a linked list to maintain the references, + * so they scale better. Also, the Tcl procedures have a bad behavior + * during the "exit" command. Their exit handler shuts them down + * when other data is still being reference-counted and cleaned up. + * + * ------------------------------------------------------------------------ + * Itcl_EventuallyFree() + * + * Registers a piece of data so that it will be freed when no longer + * in use. The data is registered with an initial usage count of "0". + * Future calls to Itcl_PreserveData() increase this usage count, and + * calls to Itcl_ReleaseData() decrease the count until it reaches + * zero and the data is freed. + * ------------------------------------------------------------------------ + */ +void +Itcl_EventuallyFree(cdata, fproc) + ClientData cdata; /* data to be freed when not in use */ + Tcl_FreeProc *fproc; /* procedure called to free data */ +{ + int newEntry; + Tcl_HashEntry *entry; + ItclPreservedData *chunk; + + /* + * If the clientData value is NULL, do nothing. + */ + if (cdata == NULL) { + return; + } + + /* + * If a list has not yet been created to manage bits of + * preserved data, then create it. + */ + if (!ItclPreservedList) { + ItclPreservedList = (Tcl_HashTable*)ckalloc( + (unsigned)sizeof(Tcl_HashTable) + ); + Tcl_InitHashTable(ItclPreservedList, TCL_ONE_WORD_KEYS); + } + + /* + * Find or create the data in the global list. + */ + entry = Tcl_CreateHashEntry(ItclPreservedList,(char*)cdata, &newEntry); + if (newEntry) { + chunk = (ItclPreservedData*)ckalloc( + (unsigned)sizeof(ItclPreservedData) + ); + chunk->data = cdata; + chunk->usage = 0; + chunk->fproc = fproc; + Tcl_SetHashValue(entry, (ClientData)chunk); + } + else { + chunk = (ItclPreservedData*)Tcl_GetHashValue(entry); + chunk->fproc = fproc; + } + + /* + * If the usage count is zero, then delete the data now. + */ + if (chunk->usage == 0) { + chunk->usage = -1; /* cannot preserve/release anymore */ + + (*chunk->fproc)((char*)chunk->data); + Tcl_DeleteHashEntry(entry); + ckfree((char*)chunk); + } +} + +/* + * ------------------------------------------------------------------------ + * Itcl_PreserveData() + * + * Increases the usage count for a piece of data that will be freed + * later when no longer needed. Each call to Itcl_PreserveData() + * puts one claim on a piece of data, and subsequent calls to + * Itcl_ReleaseData() remove those claims. When Itcl_EventuallyFree() + * is called, and when the usage count reaches zero, the data is + * freed. + * ------------------------------------------------------------------------ + */ +void +Itcl_PreserveData(cdata) + ClientData cdata; /* data to be preserved */ +{ + Tcl_HashEntry *entry; + ItclPreservedData *chunk; + int newEntry; + + /* + * If the clientData value is NULL, do nothing. + */ + if (cdata == NULL) { + return; + } + + /* + * If a list has not yet been created to manage bits of + * preserved data, then create it. + */ + if (!ItclPreservedList) { + ItclPreservedList = (Tcl_HashTable*)ckalloc( + (unsigned)sizeof(Tcl_HashTable) + ); + Tcl_InitHashTable(ItclPreservedList,TCL_ONE_WORD_KEYS); + } + + /* + * Find the data in the global list and bump its usage count. + */ + entry = Tcl_CreateHashEntry(ItclPreservedList,(char*)cdata, &newEntry); + if (newEntry) { + chunk = (ItclPreservedData*)ckalloc( + (unsigned)sizeof(ItclPreservedData) + ); + chunk->data = cdata; + chunk->usage = 0; + chunk->fproc = NULL; + Tcl_SetHashValue(entry, (ClientData)chunk); + } + else { + chunk = (ItclPreservedData*)Tcl_GetHashValue(entry); + } + + /* + * Only increment the usage if it is non-negative. + * Negative numbers mean that the data is in the process + * of being destroyed by Itcl_ReleaseData(), and should + * not be further preserved. + */ + if (chunk->usage >= 0) { + chunk->usage++; + } +} + +/* + * ------------------------------------------------------------------------ + * Itcl_ReleaseData() + * + * Decreases the usage count for a piece of data that was registered + * previously via Itcl_PreserveData(). After Itcl_EventuallyFree() + * is called and the usage count reaches zero, the data is + * automatically freed. + * ------------------------------------------------------------------------ + */ +void +Itcl_ReleaseData(cdata) + ClientData cdata; /* data to be released */ +{ + Tcl_HashEntry *entry; + ItclPreservedData *chunk; + + /* + * If the clientData value is NULL, do nothing. + */ + if (cdata == NULL) { + return; + } + + /* + * Otherwise, find the data in the global list and + * decrement its usage count. + */ + entry = NULL; + if (ItclPreservedList) { + entry = Tcl_FindHashEntry(ItclPreservedList,(char*)cdata); + } + if (!entry) { + panic("Itcl_ReleaseData can't find reference for 0x%x", cdata); + } + + /* + * Only decrement the usage if it is non-negative. + * When the usage reaches zero, set it to a negative number + * to indicate that data is being destroyed, and then + * invoke the client delete proc. When the data is deleted, + * remove the entry from the preservation list. + */ + chunk = (ItclPreservedData*)Tcl_GetHashValue(entry); + if (chunk->usage > 0 && --chunk->usage == 0) { + + if (chunk->fproc) { + chunk->usage = -1; /* cannot preserve/release anymore */ + (*chunk->fproc)((char*)chunk->data); + } + + Tcl_DeleteHashEntry(entry); + ckfree((char*)chunk); + } +} + + +/* + * ------------------------------------------------------------------------ + * Itcl_SaveInterpState() + * + * Takes a snapshot of the current result state of the interpreter. + * The snapshot can be restored at any point by Itcl_RestoreInterpState. + * So if you are in the middle of building a return result, you can + * snapshot the interpreter, execute a command that might generate an + * error, restore the snapshot, and continue building the result string. + * + * Once a snapshot is saved, it must be restored by calling + * Itcl_RestoreInterpState, or discarded by calling + * Itcl_DiscardInterpState. Otherwise, memory will be leaked. + * + * Returns a token representing the state of the interpreter. + * ------------------------------------------------------------------------ + */ +Itcl_InterpState +Itcl_SaveInterpState(interp, status) + Tcl_Interp* interp; /* interpreter being modified */ + int status; /* integer status code for current operation */ +{ + Interp *iPtr = (Interp*)interp; + + InterpState *info; + char *val; + + info = (InterpState*)ckalloc(sizeof(InterpState)); + info->validate = TCL_STATE_VALID; + info->status = status; + info->errorInfo = NULL; + info->errorCode = NULL; + + /* + * Get the result object from the interpreter. This synchronizes + * the old-style result, so we don't have to worry about it. + * Keeping the object result is enough. + */ + info->objResult = Tcl_GetObjResult(interp); + Tcl_IncrRefCount(info->objResult); + + /* + * If an error is in progress, preserve its state. + */ + if ((iPtr->flags & ERR_IN_PROGRESS) != 0) { + val = Tcl_GetVar(interp, "errorInfo", TCL_GLOBAL_ONLY); + if (val) { + info->errorInfo = ckalloc((unsigned)(strlen(val)+1)); + strcpy(info->errorInfo, val); + } + + val = Tcl_GetVar(interp, "errorCode", TCL_GLOBAL_ONLY); + if (val) { + info->errorCode = ckalloc((unsigned)(strlen(val)+1)); + strcpy(info->errorCode, val); + } + } + + /* + * Now, reset the interpreter to a clean state. + */ + Tcl_ResetResult(interp); + + return (Itcl_InterpState)info; +} + + +/* + * ------------------------------------------------------------------------ + * Itcl_RestoreInterpState() + * + * Restores the state of the interpreter to a snapshot taken by + * Itcl_SaveInterpState. This affects variables such as "errorInfo" + * and "errorCode". After this call, the token for the interpreter + * state is no longer valid. + * + * Returns the status code that was pending at the time the state was + * captured. + * ------------------------------------------------------------------------ + */ +int +Itcl_RestoreInterpState(interp, state) + Tcl_Interp* interp; /* interpreter being modified */ + Itcl_InterpState state; /* token representing interpreter state */ +{ + Interp *iPtr = (Interp*)interp; + InterpState *info = (InterpState*)state; + int status; + + if (info->validate != TCL_STATE_VALID) { + panic("bad token in Itcl_RestoreInterpState"); + } + Tcl_ResetResult(interp); + + /* + * If an error is in progress, restore its state. + * Set the error code the hard way--set the variable directly + * and fix the interpreter flags. Otherwise, if the error code + * string is really a list, it will get wrapped in extra {}'s. + */ + if (info->errorInfo) { + Tcl_AddErrorInfo(interp, info->errorInfo); + ckfree(info->errorInfo); + } + + if (info->errorCode) { + (void) Tcl_SetVar2(interp, "errorCode", (char*)NULL, + info->errorCode, TCL_GLOBAL_ONLY); + iPtr->flags |= ERROR_CODE_SET; + + ckfree(info->errorCode); + } + + /* + * Assign the object result back to the interpreter, then + * release our hold on it. + */ + Tcl_SetObjResult(interp, info->objResult); + Tcl_DecrRefCount(info->objResult); + + status = info->status; + info->validate = 0; + ckfree((char*)info); + + return status; +} + + +/* + * ------------------------------------------------------------------------ + * Itcl_DiscardInterpState() + * + * Frees the memory associated with an interpreter snapshot taken by + * Itcl_SaveInterpState. If the snapshot is not restored, this + * procedure must be called to discard it, or the memory will be lost. + * After this call, the token for the interpreter state is no longer + * valid. + * ------------------------------------------------------------------------ + */ +void +Itcl_DiscardInterpState(state) + Itcl_InterpState state; /* token representing interpreter state */ +{ + InterpState *info = (InterpState*)state; + + if (info->validate != TCL_STATE_VALID) { + panic("bad token in Itcl_DiscardInterpState"); + } + + if (info->errorInfo) { + ckfree(info->errorInfo); + } + if (info->errorCode) { + ckfree(info->errorCode); + } + Tcl_DecrRefCount(info->objResult); + + info->validate = 0; + ckfree((char*)info); +} + + +/* + * ------------------------------------------------------------------------ + * Itcl_Protection() + * + * Used to query/set the protection level used when commands/variables + * are defined within a class. The default protection level (when + * no public/protected/private command is active) is ITCL_DEFAULT_PROTECT. + * In the default case, new commands are treated as public, while new + * variables are treated as protected. + * + * If the specified level is 0, then this procedure returns the + * current value without changing it. Otherwise, it sets the current + * value to the specified protection level, and returns the previous + * value. + * ------------------------------------------------------------------------ + */ +int +Itcl_Protection(interp, newLevel) + Tcl_Interp *interp; /* interpreter being queried */ + int newLevel; /* new protection level or 0 */ +{ + int oldVal; + ItclObjectInfo *info; + + /* + * If a new level was specified, then set the protection level. + * In any case, return the protection level as it stands right now. + */ + info = (ItclObjectInfo*) Tcl_GetAssocData(interp, ITCL_INTERP_DATA, + (Tcl_InterpDeleteProc**)NULL); + + assert(info != NULL); + oldVal = info->protection; + + if (newLevel != 0) { + assert(newLevel == ITCL_PUBLIC || + newLevel == ITCL_PROTECTED || + newLevel == ITCL_PRIVATE || + newLevel == ITCL_DEFAULT_PROTECT); + info->protection = newLevel; + } + return oldVal; +} + + +/* + * ------------------------------------------------------------------------ + * Itcl_ProtectionStr() + * + * Converts an integer protection code (ITCL_PUBLIC, ITCL_PROTECTED, + * or ITCL_PRIVATE) into a human-readable character string. Returns + * a pointer to this string. + * ------------------------------------------------------------------------ + */ +char* +Itcl_ProtectionStr(pLevel) + int pLevel; /* protection level */ +{ + switch (pLevel) { + case ITCL_PUBLIC: + return "public"; + case ITCL_PROTECTED: + return "protected"; + case ITCL_PRIVATE: + return "private"; + } + return ""; +} + + +/* + * ------------------------------------------------------------------------ + * Itcl_CanAccess() + * + * Checks to see if a class member can be accessed from a particular + * namespace context. Public things can always be accessed. Protected + * things can be accessed if the "from" namespace appears in the + * inheritance hierarchy of the class namespace. Private things + * can be accessed only if the "from" namespace is the same as the + * class that contains them. + * + * Returns 1/0 indicating true/false. + * ------------------------------------------------------------------------ + */ +int +Itcl_CanAccess(memberPtr, fromNsPtr) + ItclMember* memberPtr; /* class member being tested */ + Tcl_Namespace* fromNsPtr; /* namespace requesting access */ +{ + ItclClass* fromCdPtr; + Tcl_HashEntry *entry; + + /* + * If the protection level is "public" or "private", then the + * answer is known immediately. + */ + if (memberPtr->protection == ITCL_PUBLIC) { + return 1; + } + else if (memberPtr->protection == ITCL_PRIVATE) { + return (memberPtr->classDefn->namesp == fromNsPtr); + } + + /* + * If the protection level is "protected", then check the + * heritage of the namespace requesting access. If cdefnPtr + * is in the heritage, then access is allowed. + */ + assert (memberPtr->protection == ITCL_PROTECTED); + + if (Itcl_IsClassNamespace(fromNsPtr)) { + fromCdPtr = (ItclClass*)fromNsPtr->clientData; + + entry = Tcl_FindHashEntry(&fromCdPtr->heritage, + (char*)memberPtr->classDefn); + + if (entry) { + return 1; + } + } + return 0; +} + + +/* + * ------------------------------------------------------------------------ + * Itcl_CanAccessFunc() + * + * Checks to see if a member function with the specified protection + * level can be accessed from a particular namespace context. This + * follows the same rules enforced by Itcl_CanAccess, but adds one + * special case: If the function is a protected method, and if the + * current context is a base class that has the same method, then + * access is allowed. + * + * Returns 1/0 indicating true/false. + * ------------------------------------------------------------------------ + */ +int +Itcl_CanAccessFunc(mfunc, fromNsPtr) + ItclMemberFunc* mfunc; /* member function being tested */ + Tcl_Namespace* fromNsPtr; /* namespace requesting access */ +{ + ItclClass *cdPtr, *fromCdPtr; + ItclMemberFunc *ovlfunc; + Tcl_HashEntry *entry; + + /* + * Apply the usual rules first. + */ + if (Itcl_CanAccess(mfunc->member, fromNsPtr)) { + return 1; + } + + /* + * As a last resort, see if the namespace is really a base + * class of the class containing the method. Look for a + * method with the same name in the base class. If there + * is one, then this method overrides it, and the base class + * has access. + */ + if ((mfunc->member->flags & ITCL_COMMON) == 0 && + Itcl_IsClassNamespace(fromNsPtr)) { + + cdPtr = mfunc->member->classDefn; + fromCdPtr = (ItclClass*)fromNsPtr->clientData; + + if (Tcl_FindHashEntry(&cdPtr->heritage, (char*)fromCdPtr)) { + entry = Tcl_FindHashEntry(&fromCdPtr->resolveCmds, + mfunc->member->name); + + if (entry) { + ovlfunc = (ItclMemberFunc*)Tcl_GetHashValue(entry); + if ((ovlfunc->member->flags & ITCL_COMMON) == 0 && + ovlfunc->member->protection < ITCL_PRIVATE) { + return 1; + } + } + } + } + return 0; +} + + +/* + * ------------------------------------------------------------------------ + * Itcl_GetTrueNamespace() + * + * Returns the current namespace context. This procedure is similar + * to Tcl_GetCurrentNamespace, but it supports the notion of + * "transparent" call frames installed by Itcl_HandleInstance. + * + * Returns a pointer to the current namespace calling context. + * ------------------------------------------------------------------------ + */ +Tcl_Namespace* +Itcl_GetTrueNamespace(interp, info) + Tcl_Interp *interp; /* interpreter being queried */ + ItclObjectInfo *info; /* object info associated with interp */ +{ + int i, transparent; + Tcl_CallFrame *framePtr, *transFramePtr; + Tcl_Namespace *contextNs; + + /* + * See if the current call frame is on the list of transparent + * call frames. + */ + transparent = 0; + + framePtr = _Tcl_GetCallFrame(interp, 0); + for (i = Itcl_GetStackSize(&info->transparentFrames)-1; i >= 0; i--) { + transFramePtr = (Tcl_CallFrame*) + Itcl_GetStackValue(&info->transparentFrames, i); + + if (framePtr == transFramePtr) { + transparent = 1; + break; + } + } + + /* + * If this is a transparent call frame, return the namespace + * context one level up. + */ + if (transparent) { + framePtr = _Tcl_GetCallFrame(interp, 1); + if (framePtr) { + contextNs = framePtr->nsPtr; + } else { + contextNs = Tcl_GetGlobalNamespace(interp); + } + } + else { + contextNs = Tcl_GetCurrentNamespace(interp); + } + return contextNs; +} + + +/* + * ------------------------------------------------------------------------ + * Itcl_ParseNamespPath() + * + * Parses a reference to a namespace element of the form: + * + * namesp::namesp::namesp::element + * + * Returns pointers to the head part ("namesp::namesp::namesp") + * and the tail part ("element"). If the head part is missing, + * a NULL pointer is returned and the rest of the string is taken + * as the tail. + * + * Both head and tail point to locations within the given dynamic + * string buffer. This buffer must be uninitialized when passed + * into this procedure, and it must be freed later on, when the + * strings are no longer needed. + * ------------------------------------------------------------------------ + */ +void +Itcl_ParseNamespPath(name, buffer, head, tail) + char *name; /* path name to class member */ + Tcl_DString *buffer; /* dynamic string buffer (uninitialized) */ + char **head; /* returns "namesp::namesp::namesp" part */ + char **tail; /* returns "element" part */ +{ + register char *sep; + + Tcl_DStringInit(buffer); + + /* + * Copy the name into the buffer and parse it. Look + * backward from the end of the string to the first '::' + * scope qualifier. + */ + Tcl_DStringAppend(buffer, name, -1); + name = Tcl_DStringValue(buffer); + + for (sep=name; *sep != '\0'; sep++) + ; + + while (--sep > name) { + if (*sep == ':' && *(sep-1) == ':') { + break; + } + } + + /* + * Found head/tail parts. If there are extra :'s, keep backing + * up until the head is found. This supports the Tcl namespace + * behavior, which allows names like "foo:::bar". + */ + if (sep > name) { + *tail = sep+1; + while (sep > name && *(sep-1) == ':') { + sep--; + } + *sep = '\0'; + *head = name; + } + + /* + * No :: separators--the whole name is treated as a tail. + */ + else { + *tail = name; + *head = NULL; + } +} + + +/* + * ------------------------------------------------------------------------ + * Itcl_DecodeScopedCommand() + * + * Decodes a scoped command of the form: + * + * namespace inscope + * + * If the given string is not a scoped value, this procedure does + * nothing and returns TCL_OK. If the string is a scoped value, + * then it is decoded, and the namespace, and the simple command + * string are returned as arguments; the simple command should + * be freed when no longer in use. If anything goes wrong, this + * procedure returns TCL_ERROR, along with an error message in + * the interpreter. + * ------------------------------------------------------------------------ + */ +int +Itcl_DecodeScopedCommand(interp, name, rNsPtr, rCmdPtr) + Tcl_Interp *interp; /* current interpreter */ + char *name; /* string to be decoded */ + Tcl_Namespace **rNsPtr; /* returns: namespace for scoped value */ + char **rCmdPtr; /* returns: simple command word */ +{ + Tcl_Namespace *nsPtr = NULL; + char *cmdName = name; + int len = strlen(name); + + char *pos; + int listc, result; + char **listv; + + if ((*name == 'n') && (len > 17) && (strncmp(name, "namespace", 9) == 0)) { + for (pos = (name + 9); (*pos == ' '); pos++) { + /* empty body: skip over spaces */ + } + if ((*pos == 'i') && ((pos + 7) <= (name + len)) + && (strncmp(pos, "inscope", 7) == 0)) { + + result = Tcl_SplitList(interp, name, &listc, &listv); + if (result == TCL_OK) { + if (listc != 4) { + Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), + "malformed command \"", name, "\": should be \"", + "namespace inscope namesp command\"", + (char*)NULL); + result = TCL_ERROR; + } + else { + nsPtr = Tcl_FindNamespace(interp, listv[2], + (Tcl_Namespace*)NULL, TCL_LEAVE_ERR_MSG); + + if (!nsPtr) { + result = TCL_ERROR; + } + else { + cmdName = ckalloc((unsigned)(strlen(listv[3])+1)); + strcpy(cmdName, listv[3]); + } + } + } + ckfree((char*)listv); + + if (result != TCL_OK) { + char msg[512]; + sprintf(msg, "\n (while decoding scoped command \"%.400s\")", name); + Tcl_AddObjErrorInfo(interp, msg, -1); + return TCL_ERROR; + } + } + } + + *rNsPtr = nsPtr; + *rCmdPtr = cmdName; + return TCL_OK; +} + + +/* + * ------------------------------------------------------------------------ + * Itcl_EvalArgs() + * + * This procedure invokes a list of (objc,objv) arguments as a + * single command. It is similar to Tcl_EvalObj, but it doesn't + * do any parsing or compilation. It simply treats the first + * argument as a command and invokes that command in the current + * context. + * + * Returns TCL_OK if successful. Otherwise, this procedure returns + * TCL_ERROR along with an error message in the interpreter. + * ------------------------------------------------------------------------ + */ +int +Itcl_EvalArgs(interp, objc, objv) + Tcl_Interp *interp; /* current interpreter */ + int objc; /* number of arguments */ + Tcl_Obj *CONST objv[]; /* argument objects */ +{ + int result; + Tcl_Command cmd; + Command *cmdPtr; + int cmdlinec; + Tcl_Obj **cmdlinev; + Tcl_Obj *cmdlinePtr = NULL; + + /* + * Resolve the command by converting it to a CmdName object. + * This caches a pointer to the Command structure for the + * command, so if we need it again, it's ready to use. + */ + cmd = Tcl_GetCommandFromObj(interp, objv[0]); + cmdPtr = (Command*)cmd; + + cmdlinec = objc; + cmdlinev = (Tcl_Obj**)objv; + + /* + * If the command is still not found, handle it with the + * "unknown" proc. + */ + if (cmdPtr == NULL) { + cmd = Tcl_FindCommand(interp, "unknown", + (Tcl_Namespace *) NULL, /*flags*/ TCL_GLOBAL_ONLY); + + if (cmd == NULL) { + Tcl_ResetResult(interp); + Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), + "invalid command name \"", + Tcl_GetStringFromObj(objv[0], (int*)NULL), "\"", + (char*)NULL); + return TCL_ERROR; + } + cmdPtr = (Command*)cmd; + + cmdlinePtr = Itcl_CreateArgs(interp, "unknown", objc, objv); + + (void) Tcl_ListObjGetElements((Tcl_Interp*)NULL, cmdlinePtr, + &cmdlinec, &cmdlinev); + } + + /* + * Finally, invoke the command's Tcl_ObjCmdProc. Be careful + * to pass in the proper client data. + */ + Tcl_ResetResult(interp); + result = (*cmdPtr->objProc)(cmdPtr->objClientData, interp, + cmdlinec, cmdlinev); + + if (cmdlinePtr) { + Tcl_DecrRefCount(cmdlinePtr); + } + return result; +} + + +/* + * ------------------------------------------------------------------------ + * Itcl_CreateArgs() + * + * This procedure takes a string and a list of (objc,objv) arguments, + * and glues them together in a single list. This is useful when + * a command word needs to be prepended or substituted into a command + * line before it is executed. The arguments are returned in a single + * list object, and they can be retrieved by calling + * Tcl_ListObjGetElements. When the arguments are no longer needed, + * they should be discarded by decrementing the reference count for + * the list object. + * + * Returns a pointer to the list object containing the arguments. + * ------------------------------------------------------------------------ + */ +Tcl_Obj* +Itcl_CreateArgs(interp, string, objc, objv) + Tcl_Interp *interp; /* current interpreter */ + char *string; /* first command word */ + int objc; /* number of arguments */ + Tcl_Obj *CONST objv[]; /* argument objects */ +{ + int i; + Tcl_Obj *listPtr; + + listPtr = Tcl_NewListObj(0, (Tcl_Obj**)NULL); + Tcl_ListObjAppendElement((Tcl_Interp*)NULL, listPtr, + Tcl_NewStringObj(string, -1)); + + for (i=0; i < objc; i++) { + Tcl_ListObjAppendElement((Tcl_Interp*)NULL, listPtr, objv[i]); + } + + Tcl_IncrRefCount(listPtr); + return listPtr; +}
itcl_util.c Property changes : Added: svn:executable ## -0,0 +1 ## +* \ No newline at end of property Index: itclInt.h =================================================================== --- itclInt.h (nonexistent) +++ itclInt.h (revision 1765) @@ -0,0 +1,535 @@ +/* + * ------------------------------------------------------------------------ + * PACKAGE: [incr Tcl] + * DESCRIPTION: Object-Oriented Extensions to Tcl + * + * [incr Tcl] provides object-oriented extensions to Tcl, much as + * C++ provides object-oriented extensions to C. It provides a means + * of encapsulating related procedures together with their shared data + * in a local namespace that is hidden from the outside world. It + * promotes code re-use through inheritance. More than anything else, + * it encourages better organization of Tcl applications through the + * object-oriented paradigm, leading to code that is easier to + * understand and maintain. + * + * ADDING [incr Tcl] TO A Tcl-BASED APPLICATION: + * + * To add [incr Tcl] facilities to a Tcl application, modify the + * Tcl_AppInit() routine as follows: + * + * 1) Include this header file near the top of the file containing + * Tcl_AppInit(): + * + * #include "itcl.h" + * + * 2) Within the body of Tcl_AppInit(), add the following lines: + * + * if (Itcl_Init(interp) == TCL_ERROR) { + * return TCL_ERROR; + * } + * + * 3) Link your application with libitcl.a + * + * NOTE: An example file "tclAppInit.c" containing the changes shown + * above is included in this distribution. + * + * ======================================================================== + * AUTHOR: Michael J. McLennan + * Bell Labs Innovations for Lucent Technologies + * mmclennan@lucent.com + * http://www.tcltk.com/itcl + * + * RCS: $Id: itclInt.h,v 1.1.1.1 2002-01-16 10:24:46 markom Exp $ + * ======================================================================== + * Copyright (c) 1993-1998 Lucent Technologies, Inc. + * ------------------------------------------------------------------------ + * See the file "license.terms" for information on usage and redistribution + * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + */ +#ifndef ITCLINT_H +#define ITCLINT_H + +#include "itcl.h" +#include "tclInt.h" + +#ifdef BUILD_itcl +# undef TCL_STORAGE_CLASS +# define TCL_STORAGE_CLASS DLLEXPORT +#endif + +/* + * Since the Tcl/Tk distribution doesn't perform any asserts, + * dynamic loading can fail to find the __assert function. + * As a workaround, we'll include our own. + */ +#undef assert +#ifdef NDEBUG +#define assert(EX) ((void)0) +#else +EXTERN void Itcl_Assert _ANSI_ARGS_((char *testExpr, char *fileName, int lineNum) +); +#if defined(__STDC__) +#define assert(EX) (void)((EX) || (Itcl_Assert(#EX, __FILE__, __LINE__), 0)) +#else +#define assert(EX) (void)((EX) || (Itcl_Assert("EX", __FILE__, __LINE__), 0)) +#endif /* __STDC__ */ +#endif /* NDEBUG */ + + +/* + * Common info for managing all known objects. + * Each interpreter has one of these data structures stored as + * clientData in the "itcl" namespace. It is also accessible + * as associated data via the key ITCL_INTERP_DATA. + */ +struct ItclObject; +typedef struct ItclObjectInfo { + Tcl_Interp *interp; /* interpreter that manages this info */ + Tcl_HashTable objects; /* list of all known objects */ + + Itcl_Stack transparentFrames; /* stack of call frames that should be + * treated transparently. When + * Itcl_EvalMemberCode is invoked in + * one of these contexts, it does an + * "uplevel" to get past the transparent + * frame and back to the calling context. */ + Tcl_HashTable contextFrames; /* object contexts for active call frames */ + + int protection; /* protection level currently in effect */ + + Itcl_Stack cdefnStack; /* stack of class definitions currently + * being parsed */ +} ItclObjectInfo; + +#define ITCL_INTERP_DATA "itcl_data" + +/* + * Representation for each [incr Tcl] class. + */ +typedef struct ItclClass { + char *name; /* class name */ + char *fullname; /* fully qualified class name */ + Tcl_Interp *interp; /* interpreter that manages this info */ + Tcl_Namespace *namesp; /* namespace representing class scope */ + Tcl_Command accessCmd; /* access command for creating instances */ + + struct ItclObjectInfo *info; /* info about all known objects */ + Itcl_List bases; /* list of base classes */ + Itcl_List derived; /* list of all derived classes */ + Tcl_HashTable heritage; /* table of all base classes. Look up + * by pointer to class definition. This + * provides fast lookup for inheritance + * tests. */ + Tcl_Obj *initCode; /* initialization code for new objs */ + Tcl_HashTable variables; /* definitions for all data members + in this class. Look up simple string + names and get back ItclVarDefn* ptrs */ + Tcl_HashTable functions; /* definitions for all member functions + in this class. Look up simple string + names and get back ItclMemberFunc* ptrs */ + int numInstanceVars; /* number of instance vars in variables + table */ + Tcl_HashTable resolveVars; /* all possible names for variables in + * this class (e.g., x, foo::x, etc.) */ + Tcl_HashTable resolveCmds; /* all possible names for functions in + * this class (e.g., x, foo::x, etc.) */ + int unique; /* unique number for #auto generation */ + int flags; /* maintains class status */ +} ItclClass; + +typedef struct ItclHierIter { + ItclClass *current; /* current position in hierarchy */ + Itcl_Stack stack; /* stack used for traversal */ +} ItclHierIter; + +/* + * Representation for each [incr Tcl] object. + */ +typedef struct ItclObject { + ItclClass *classDefn; /* most-specific class */ + Tcl_Command accessCmd; /* object access command */ + + int dataSize; /* number of elements in data array */ + Var** data; /* all object-specific data members */ + Tcl_HashTable* constructed; /* temp storage used during construction */ + Tcl_HashTable* destructed; /* temp storage used during destruction */ +} ItclObject; + +#define ITCL_IGNORE_ERRS 0x002 /* useful for construction/destruction */ + +/* + * Implementation for any code body in an [incr Tcl] class. + */ +typedef struct ItclMemberCode { + int flags; /* flags describing implementation */ + CompiledLocal *arglist; /* list of arg names and initial values */ + int argcount; /* number of args in arglist */ + Proc *procPtr; /* Tcl proc representation (needed to + * handle compiled locals) */ + union { + Tcl_CmdProc *argCmd; /* (argc,argv) C implementation */ + Tcl_ObjCmdProc *objCmd; /* (objc,objv) C implementation */ + } cfunc; + + ClientData clientData; /* client data for C implementations */ + +} ItclMemberCode; + +/* + * Basic representation for class members (commands/variables) + */ +typedef struct ItclMember { + Tcl_Interp* interp; /* interpreter containing the class */ + ItclClass* classDefn; /* class containing this member */ + char* name; /* member name */ + char* fullname; /* member name with "class::" qualifier */ + int protection; /* protection level */ + int flags; /* flags describing member (see below) */ + ItclMemberCode *code; /* code associated with member */ +} ItclMember; + +/* + * Flag bits for ItclMemberCode and ItclMember: + */ +#define ITCL_IMPLEMENT_NONE 0x001 /* no implementation */ +#define ITCL_IMPLEMENT_TCL 0x002 /* Tcl implementation */ +#define ITCL_IMPLEMENT_ARGCMD 0x004 /* (argc,argv) C implementation */ +#define ITCL_IMPLEMENT_OBJCMD 0x008 /* (objc,objv) C implementation */ +#define ITCL_IMPLEMENT_C 0x00c /* either kind of C implementation */ +#define ITCL_CONSTRUCTOR 0x010 /* non-zero => is a constructor */ +#define ITCL_DESTRUCTOR 0x020 /* non-zero => is a destructor */ +#define ITCL_COMMON 0x040 /* non-zero => is a "proc" */ +#define ITCL_ARG_SPEC 0x080 /* non-zero => has an argument spec */ + +#define ITCL_OLD_STYLE 0x100 /* non-zero => old-style method + * (process "config" argument) */ + +#define ITCL_THIS_VAR 0x200 /* non-zero => built-in "this" variable */ + +/* + * Representation of member functions in an [incr Tcl] class. + */ +typedef struct ItclMemberFunc { + ItclMember *member; /* basic member info */ + Tcl_Command accessCmd; /* Tcl command installed for this function */ + CompiledLocal *arglist; /* list of arg names and initial values */ + int argcount; /* number of args in arglist */ +} ItclMemberFunc; + +/* + * Instance variables. + */ +typedef struct ItclVarDefn { + ItclMember *member; /* basic member info */ + char* init; /* initial value */ +} ItclVarDefn; + +/* + * Instance variable lookup entry. + */ +typedef struct ItclVarLookup { + ItclVarDefn* vdefn; /* variable definition */ + int usage; /* number of uses for this record */ + int accessible; /* non-zero => accessible from class with + * this lookup record in its resolveVars */ + char *leastQualName; /* simplist name for this variable, with + * the fewest qualifiers. This string is + * taken from the resolveVars table, so + * it shouldn't be freed. */ + union { + int index; /* index into virtual table (instance data) */ + Tcl_Var common; /* variable (common data) */ + } var; +} ItclVarLookup; + +/* + * Representation for the context in which a body of [incr Tcl] + * code executes. In ordinary Tcl, this is a CallFrame. But for + * [incr Tcl] code bodies, we must be careful to set up the + * CallFrame properly, to plug in instance variables before + * executing the code body. + */ +typedef struct ItclContext { + ItclClass *classDefn; /* class definition */ + CallFrame frame; /* call frame for object context */ + Var *compiledLocals; /* points to storage for compiled locals */ + Var localStorage[20]; /* default storage for compiled locals */ +} ItclContext; + + +/* + * Functions used within the package, but not considered "public" + */ + +EXTERN int Itcl_IsClassNamespace _ANSI_ARGS_((Tcl_Namespace *namesp)); +EXTERN int Itcl_IsClass _ANSI_ARGS_((Tcl_Command cmd)); +EXTERN ItclClass* Itcl_FindClass _ANSI_ARGS_((Tcl_Interp* interp, + char* path, int autoload)); + +EXTERN int Itcl_FindObject _ANSI_ARGS_((Tcl_Interp *interp, + char *name, ItclObject **roPtr)); +EXTERN int Itcl_IsObject _ANSI_ARGS_((Tcl_Command cmd)); +EXTERN int Itcl_ObjectIsa _ANSI_ARGS_((ItclObject *contextObj, + ItclClass *cdefn)); + + +EXTERN int Itcl_Protection _ANSI_ARGS_((Tcl_Interp *interp, + int newLevel)); +EXTERN char* Itcl_ProtectionStr _ANSI_ARGS_((int pLevel)); +EXTERN int Itcl_CanAccess _ANSI_ARGS_((ItclMember* memberPtr, + Tcl_Namespace* fromNsPtr)); +EXTERN int Itcl_CanAccessFunc _ANSI_ARGS_((ItclMemberFunc* mfunc, + Tcl_Namespace* fromNsPtr)); +EXTERN Tcl_Namespace* Itcl_GetTrueNamespace _ANSI_ARGS_((Tcl_Interp *interp, + ItclObjectInfo *info)); + +EXTERN void Itcl_ParseNamespPath _ANSI_ARGS_((char *name, + Tcl_DString *buffer, char **head, char **tail)); +EXTERN int Itcl_DecodeScopedCommand _ANSI_ARGS_((Tcl_Interp *interp, + char *name, Tcl_Namespace **rNsPtr, char **rCmdPtr)); +EXTERN int Itcl_EvalArgs _ANSI_ARGS_((Tcl_Interp *interp, int objc, + Tcl_Obj *CONST objv[])); +EXTERN Tcl_Obj* Itcl_CreateArgs _ANSI_ARGS_((Tcl_Interp *interp, + char *string, int objc, Tcl_Obj *CONST objv[])); + +EXTERN int Itcl_PushContext _ANSI_ARGS_((Tcl_Interp *interp, + ItclMember *member, ItclClass *contextClass, ItclObject *contextObj, + ItclContext *contextPtr)); +EXTERN void Itcl_PopContext _ANSI_ARGS_((Tcl_Interp *interp, + ItclContext *contextPtr)); +EXTERN int Itcl_GetContext _ANSI_ARGS_((Tcl_Interp *interp, + ItclClass **cdefnPtr, ItclObject **odefnPtr)); + +EXTERN void Itcl_InitHierIter _ANSI_ARGS_((ItclHierIter *iter, + ItclClass *cdefn)); +EXTERN void Itcl_DeleteHierIter _ANSI_ARGS_((ItclHierIter *iter)); +EXTERN ItclClass* Itcl_AdvanceHierIter _ANSI_ARGS_((ItclHierIter *iter)); + +EXTERN int Itcl_FindClassesCmd _ANSI_ARGS_((ClientData clientData, + Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])); +EXTERN int Itcl_FindObjectsCmd _ANSI_ARGS_((ClientData clientData, + Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])); +EXTERN int Itcl_ProtectionCmd _ANSI_ARGS_((ClientData clientData, + Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])); +EXTERN int Itcl_DelClassCmd _ANSI_ARGS_((ClientData clientData, + Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])); +EXTERN int Itcl_DelObjectCmd _ANSI_ARGS_((ClientData clientData, + Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])); +EXTERN int Itcl_ScopeCmd _ANSI_ARGS_((ClientData clientData, + Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])); +EXTERN int Itcl_CodeCmd _ANSI_ARGS_((ClientData clientData, + Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])); +EXTERN int Itcl_StubCreateCmd _ANSI_ARGS_((ClientData clientData, + Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])); +EXTERN int Itcl_StubExistsCmd _ANSI_ARGS_((ClientData clientData, + Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])); +EXTERN int Itcl_IsStub _ANSI_ARGS_((Tcl_Command cmd)); + + +/* + * Functions for manipulating classes + */ +EXTERN int Itcl_CreateClass _ANSI_ARGS_((Tcl_Interp* interp, char* path, + ItclObjectInfo *info, ItclClass **rPtr)); +EXTERN int Itcl_DeleteClass _ANSI_ARGS_((Tcl_Interp *interp, + ItclClass *cdefnPtr)); +EXTERN Tcl_Namespace* Itcl_FindClassNamespace _ANSI_ARGS_((Tcl_Interp* interp, + char* path)); +EXTERN int Itcl_HandleClass _ANSI_ARGS_((ClientData clientData, + Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])); +EXTERN int Itcl_ClassCmdResolver _ANSI_ARGS_((Tcl_Interp *interp, + char* name, Tcl_Namespace *context, int flags, Tcl_Command *rPtr)); +EXTERN int Itcl_ClassVarResolver _ANSI_ARGS_((Tcl_Interp *interp, + char* name, Tcl_Namespace *context, int flags, Tcl_Var *rPtr)); +EXTERN int Itcl_ClassCompiledVarResolver _ANSI_ARGS_((Tcl_Interp *interp, + char* name, int length, Tcl_Namespace *context, Tcl_ResolvedVarInfo **rPtr)); +EXTERN void Itcl_BuildVirtualTables _ANSI_ARGS_((ItclClass* cdefnPtr)); +EXTERN int Itcl_CreateVarDefn _ANSI_ARGS_((Tcl_Interp *interp, + ItclClass* cdefn, char* name, char* init, char* config, + ItclVarDefn** vdefnPtr)); +EXTERN void Itcl_DeleteVarDefn _ANSI_ARGS_((ItclVarDefn *vdefn)); +EXTERN char* Itcl_GetCommonVar _ANSI_ARGS_((Tcl_Interp *interp, + char *name, ItclClass *contextClass)); +EXTERN ItclMember* Itcl_CreateMember _ANSI_ARGS_((Tcl_Interp* interp, + ItclClass *cdefn, char* name)); +EXTERN void Itcl_DeleteMember _ANSI_ARGS_((ItclMember *memPtr)); + + +/* + * Functions for manipulating objects + */ +EXTERN int Itcl_CreateObject _ANSI_ARGS_((Tcl_Interp *interp, + char* name, ItclClass *cdefn, int objc, Tcl_Obj *CONST objv[], + ItclObject **roPtr)); +EXTERN int Itcl_DeleteObject _ANSI_ARGS_((Tcl_Interp *interp, + ItclObject *contextObj)); +EXTERN int Itcl_DestructObject _ANSI_ARGS_((Tcl_Interp *interp, + ItclObject *contextObj, int flags)); +EXTERN int Itcl_HandleInstance _ANSI_ARGS_((ClientData clientData, + Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])); +EXTERN char* Itcl_GetInstanceVar _ANSI_ARGS_((Tcl_Interp *interp, + char *name, ItclObject *contextObj, ItclClass *contextClass)); +EXTERN int Itcl_ScopedVarResolver _ANSI_ARGS_((Tcl_Interp *interp, + char *name, Tcl_Namespace *contextNs, int flags, Tcl_Var *rPtr)); + + +/* + * Functions for manipulating methods and procs + */ +EXTERN int Itcl_BodyCmd _ANSI_ARGS_((ClientData dummy, + Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])); +EXTERN int Itcl_ConfigBodyCmd _ANSI_ARGS_((ClientData dummy, + Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])); +EXTERN int Itcl_CreateMethod _ANSI_ARGS_((Tcl_Interp* interp, + ItclClass *cdefn, char* name, char* arglist, char* body)); +EXTERN int Itcl_CreateProc _ANSI_ARGS_((Tcl_Interp* interp, + ItclClass *cdefn, char* name, char* arglist, char* body)); +EXTERN int Itcl_CreateMemberFunc _ANSI_ARGS_((Tcl_Interp* interp, + ItclClass *cdefn, char* name, char* arglist, char* body, + ItclMemberFunc** mfuncPtr)); +EXTERN int Itcl_ChangeMemberFunc _ANSI_ARGS_((Tcl_Interp* interp, + ItclMemberFunc* mfunc, char* arglist, char* body)); +EXTERN void Itcl_DeleteMemberFunc _ANSI_ARGS_((char* cdata)); +EXTERN int Itcl_CreateMemberCode _ANSI_ARGS_((Tcl_Interp* interp, + ItclClass *cdefn, char* arglist, char* body, ItclMemberCode** mcodePtr)); +EXTERN void Itcl_DeleteMemberCode _ANSI_ARGS_((char* cdata)); +EXTERN int Itcl_GetMemberCode _ANSI_ARGS_((Tcl_Interp* interp, + ItclMember* member)); +EXTERN int Itcl_CompileMemberCodeBody _ANSI_ARGS_((Tcl_Interp *interp, + ItclMember *member, char *desc, Tcl_Obj *bodyPtr)); +EXTERN int Itcl_EvalMemberCode _ANSI_ARGS_((Tcl_Interp *interp, + ItclMemberFunc *mfunc, ItclMember *member, ItclObject *contextObj, + int objc, Tcl_Obj *CONST objv[])); +EXTERN int Itcl_CreateArgList _ANSI_ARGS_((Tcl_Interp* interp, + char* decl, int* argcPtr, CompiledLocal** argPtr)); +EXTERN CompiledLocal* Itcl_CreateArg _ANSI_ARGS_((char* name, + char* init)); +EXTERN void Itcl_DeleteArgList _ANSI_ARGS_((CompiledLocal *arglist)); +EXTERN Tcl_Obj* Itcl_ArgList _ANSI_ARGS_((int argc, CompiledLocal* arglist)); +EXTERN int Itcl_EquivArgLists _ANSI_ARGS_((CompiledLocal* arg1, int arg1c, + CompiledLocal* arg2, int arg2c)); +EXTERN void Itcl_GetMemberFuncUsage _ANSI_ARGS_((ItclMemberFunc *mfunc, + ItclObject *contextObj, Tcl_Obj *objPtr)); +EXTERN int Itcl_ExecMethod _ANSI_ARGS_((ClientData clientData, + Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])); +EXTERN int Itcl_ExecProc _ANSI_ARGS_((ClientData clientData, + Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])); +EXTERN int Itcl_AssignArgs _ANSI_ARGS_((Tcl_Interp *interp, + int objc, Tcl_Obj *CONST objv[], ItclMemberFunc *mfunc)); +EXTERN int Itcl_ConstructBase _ANSI_ARGS_((Tcl_Interp *interp, + ItclObject *contextObj, ItclClass *contextClass)); +EXTERN int Itcl_InvokeMethodIfExists _ANSI_ARGS_((Tcl_Interp *interp, + char *name, ItclClass *contextClass, ItclObject *contextObj, + int objc, Tcl_Obj *CONST objv[])); +EXTERN int Itcl_EvalBody _ANSI_ARGS_((Tcl_Interp *interp, + Tcl_Obj *bodyPtr)); +EXTERN int Itcl_ReportFuncErrors _ANSI_ARGS_((Tcl_Interp* interp, + ItclMemberFunc *mfunc, ItclObject *contextObj, int result)); + + +/* + * Commands for parsing class definitions + */ +EXTERN int Itcl_ParseInit _ANSI_ARGS_((Tcl_Interp *interp, + ItclObjectInfo *info)); +EXTERN int Itcl_ClassCmd _ANSI_ARGS_((ClientData clientData, + Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])); +EXTERN int Itcl_ClassInheritCmd _ANSI_ARGS_((ClientData clientData, + Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])); +EXTERN int Itcl_ClassProtectionCmd _ANSI_ARGS_((ClientData clientData, + Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])); +EXTERN int Itcl_ClassConstructorCmd _ANSI_ARGS_((ClientData clientData, + Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])); +EXTERN int Itcl_ClassDestructorCmd _ANSI_ARGS_((ClientData clientData, + Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])); +EXTERN int Itcl_ClassMethodCmd _ANSI_ARGS_((ClientData clientData, + Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])); +EXTERN int Itcl_ClassProcCmd _ANSI_ARGS_((ClientData clientData, + Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])); +EXTERN int Itcl_ClassVariableCmd _ANSI_ARGS_((ClientData clientData, + Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])); +EXTERN int Itcl_ClassCommonCmd _ANSI_ARGS_((ClientData clientData, + Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])); +EXTERN int Itcl_ParseVarResolver _ANSI_ARGS_((Tcl_Interp *interp, + char* name, Tcl_Namespace *contextNs, int flags, Tcl_Var* rPtr)); + + +/* + * Commands in the "builtin" namespace + */ +EXTERN int Itcl_BiInit _ANSI_ARGS_((Tcl_Interp *interp)); +EXTERN int Itcl_InstallBiMethods _ANSI_ARGS_((Tcl_Interp *interp, + ItclClass *cdefn)); +EXTERN int Itcl_BiIsaCmd _ANSI_ARGS_((ClientData clientData, + Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])); +EXTERN int Itcl_BiConfigureCmd _ANSI_ARGS_((ClientData clientData, + Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])); +EXTERN int Itcl_BiCgetCmd _ANSI_ARGS_((ClientData clientData, + Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])); +EXTERN int Itcl_BiChainCmd _ANSI_ARGS_((ClientData dummy, + Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])); +EXTERN int Itcl_BiInfoClassCmd _ANSI_ARGS_((ClientData dummy, + Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])); +EXTERN int Itcl_BiInfoInheritCmd _ANSI_ARGS_((ClientData dummy, + Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])); +EXTERN int Itcl_BiInfoHeritageCmd _ANSI_ARGS_((ClientData dummy, + Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])); +EXTERN int Itcl_BiInfoFunctionCmd _ANSI_ARGS_((ClientData dummy, + Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])); +EXTERN int Itcl_BiInfoVariableCmd _ANSI_ARGS_((ClientData dummy, + Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])); +EXTERN int Itcl_BiInfoBodyCmd _ANSI_ARGS_((ClientData dummy, + Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])); +EXTERN int Itcl_BiInfoArgsCmd _ANSI_ARGS_((ClientData dummy, + Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])); +EXTERN int Itcl_DefaultInfoCmd _ANSI_ARGS_((ClientData dummy, + Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])); + + +/* + * Ensembles + */ +EXTERN int Itcl_EnsembleInit _ANSI_ARGS_((Tcl_Interp *interp)); +EXTERN int Itcl_CreateEnsemble _ANSI_ARGS_((Tcl_Interp *interp, + char* ensName)); +EXTERN int Itcl_AddEnsemblePart _ANSI_ARGS_((Tcl_Interp *interp, + char* ensName, char* partName, char* usageInfo, + Tcl_ObjCmdProc *objProc, ClientData clientData, + Tcl_CmdDeleteProc *deleteProc)); +EXTERN int Itcl_GetEnsemblePart _ANSI_ARGS_((Tcl_Interp *interp, + char *ensName, char *partName, Tcl_CmdInfo *infoPtr)); +EXTERN int Itcl_IsEnsemble _ANSI_ARGS_((Tcl_CmdInfo* infoPtr)); +EXTERN int Itcl_GetEnsembleUsage _ANSI_ARGS_((Tcl_Interp *interp, + char *ensName, Tcl_Obj *objPtr)); +EXTERN int Itcl_GetEnsembleUsageForObj _ANSI_ARGS_((Tcl_Interp *interp, + Tcl_Obj *ensObjPtr, Tcl_Obj *objPtr)); +EXTERN int Itcl_EnsembleCmd _ANSI_ARGS_((ClientData clientData, + Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])); +EXTERN int Itcl_EnsPartCmd _ANSI_ARGS_((ClientData clientData, + Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])); +EXTERN int Itcl_EnsembleErrorCmd _ANSI_ARGS_((ClientData clientData, + Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])); + + +/* + * Commands provided for backward compatibility + */ +EXTERN int Itcl_OldInit _ANSI_ARGS_((Tcl_Interp* interp, + ItclObjectInfo* info)); +EXTERN int Itcl_InstallOldBiMethods _ANSI_ARGS_((Tcl_Interp *interp, + ItclClass *cdefn)); + + +/* + * Things that should be in the Tcl core. + */ +EXTERN Tcl_CallFrame* _Tcl_GetCallFrame _ANSI_ARGS_((Tcl_Interp *interp, + int level)); +EXTERN Tcl_CallFrame* _Tcl_ActivateCallFrame _ANSI_ARGS_((Tcl_Interp *interp, + Tcl_CallFrame *framePtr)); +EXTERN Var* _TclNewVar _ANSI_ARGS_((void)); + +#undef TCL_STORAGE_CLASS +#define TCL_STORAGE_CLASS DLLIMPORT + +#endif /* ITCLINT_H */
itclInt.h Property changes : Added: svn:executable ## -0,0 +1 ## +* \ No newline at end of property Index: itcl_bicmds.c =================================================================== --- itcl_bicmds.c (nonexistent) +++ itcl_bicmds.c (revision 1765) @@ -0,0 +1,1695 @@ +/* + * ------------------------------------------------------------------------ + * PACKAGE: [incr Tcl] + * DESCRIPTION: Object-Oriented Extensions to Tcl + * + * [incr Tcl] provides object-oriented extensions to Tcl, much as + * C++ provides object-oriented extensions to C. It provides a means + * of encapsulating related procedures together with their shared data + * in a local namespace that is hidden from the outside world. It + * promotes code re-use through inheritance. More than anything else, + * it encourages better organization of Tcl applications through the + * object-oriented paradigm, leading to code that is easier to + * understand and maintain. + * + * These procedures handle built-in class methods, including the + * "isa" method (to query hierarchy info) and the "info" method + * (to query class/object data). + * + * ======================================================================== + * AUTHOR: Michael J. McLennan + * Bell Labs Innovations for Lucent Technologies + * mmclennan@lucent.com + * http://www.tcltk.com/itcl + * + * RCS: $Id: itcl_bicmds.c,v 1.1.1.1 2002-01-16 10:24:46 markom Exp $ + * ======================================================================== + * Copyright (c) 1993-1998 Lucent Technologies, Inc. + * ------------------------------------------------------------------------ + * See the file "license.terms" for information on usage and redistribution + * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + */ +#include "itclInt.h" + +/* + * Standard list of built-in methods for all objects. + */ +typedef struct BiMethod { + char* name; /* method name */ + char* usage; /* string describing usage */ + char* registration; /* registration name for C proc */ + Tcl_ObjCmdProc *proc; /* implementation C proc */ +} BiMethod; + +static BiMethod BiMethodList[] = { + { "cget", "-option", + "@itcl-builtin-cget", Itcl_BiCgetCmd }, + { "configure", "?-option? ?value -option value...?", + "@itcl-builtin-configure", Itcl_BiConfigureCmd }, + { "isa", "className", + "@itcl-builtin-isa", Itcl_BiIsaCmd }, +}; +static int BiMethodListLen = sizeof(BiMethodList)/sizeof(BiMethod); + + +/* + * FORWARD DECLARATIONS + */ +static Tcl_Obj* ItclReportPublicOpt _ANSI_ARGS_((Tcl_Interp *interp, + ItclVarDefn *vdefn, ItclObject *contextObj)); + + +/* + * ------------------------------------------------------------------------ + * Itcl_BiInit() + * + * Creates a namespace full of built-in methods/procs for [incr Tcl] + * classes. This includes things like the "isa" method and "info" + * for querying class info. Usually invoked by Itcl_Init() when + * [incr Tcl] is first installed into an interpreter. + * + * Returns TCL_OK/TCL_ERROR to indicate success/failure. + * ------------------------------------------------------------------------ + */ +int +Itcl_BiInit(interp) + Tcl_Interp *interp; /* current interpreter */ +{ + int i; + Tcl_Namespace *itclBiNs; + + /* + * Declare all of the built-in methods as C procedures. + */ + for (i=0; i < BiMethodListLen; i++) { + if (Itcl_RegisterObjC(interp, + BiMethodList[i].registration+1, BiMethodList[i].proc, + (ClientData)NULL, (Tcl_CmdDeleteProc*)NULL) != TCL_OK) { + + return TCL_ERROR; + } + } + + /* + * Create the "::itcl::builtin" namespace for built-in class + * commands. These commands are imported into each class + * just before the class definition is parsed. + */ + Tcl_CreateObjCommand(interp, "::itcl::builtin::chain", Itcl_BiChainCmd, + (ClientData)NULL, (Tcl_CmdDeleteProc*)NULL); + + if (Itcl_CreateEnsemble(interp, "::itcl::builtin::info") != TCL_OK) { + return TCL_ERROR; + } + + if (Itcl_AddEnsemblePart(interp, "::itcl::builtin::info", + "class", "", + Itcl_BiInfoClassCmd, (ClientData)NULL, (Tcl_CmdDeleteProc*)NULL) + != TCL_OK || + Itcl_AddEnsemblePart(interp, "::itcl::builtin::info", + "inherit", "", + Itcl_BiInfoInheritCmd, (ClientData)NULL, (Tcl_CmdDeleteProc*)NULL) + != TCL_OK || + Itcl_AddEnsemblePart(interp, "::itcl::builtin::info", + "heritage", "", + Itcl_BiInfoHeritageCmd, (ClientData)NULL, (Tcl_CmdDeleteProc*)NULL) + != TCL_OK || + Itcl_AddEnsemblePart(interp, "::itcl::builtin::info", + "function", "?name? ?-protection? ?-type? ?-name? ?-args? ?-body?", + Itcl_BiInfoFunctionCmd, (ClientData)NULL, (Tcl_CmdDeleteProc*)NULL) + != TCL_OK || + Itcl_AddEnsemblePart(interp, "::itcl::builtin::info", + "variable", "?name? ?-protection? ?-type? ?-name? ?-init? ?-value? ?-config?", + Itcl_BiInfoVariableCmd, (ClientData)NULL, (Tcl_CmdDeleteProc*)NULL) + != TCL_OK || + Itcl_AddEnsemblePart(interp, "::itcl::builtin::info", + "args", "procname", + Itcl_BiInfoArgsCmd, (ClientData)NULL, (Tcl_CmdDeleteProc*)NULL) + != TCL_OK || + Itcl_AddEnsemblePart(interp, "::itcl::builtin::info", + "body", "procname", + Itcl_BiInfoBodyCmd, (ClientData)NULL, (Tcl_CmdDeleteProc*)NULL) + != TCL_OK + ) { + return TCL_ERROR; + } + + /* + * Add an error handler to support all of the usual inquiries + * for the "info" command in the global namespace. + */ + if (Itcl_AddEnsemblePart(interp, "::itcl::builtin::info", + "@error", "", + Itcl_DefaultInfoCmd, (ClientData)NULL, (Tcl_CmdDeleteProc*)NULL) + != TCL_OK + ) { + return TCL_ERROR; + } + + /* + * Export all commands in the built-in namespace so we can + * import them later on. + */ + itclBiNs = Tcl_FindNamespace(interp, "::itcl::builtin", + (Tcl_Namespace*)NULL, TCL_LEAVE_ERR_MSG); + + if (!itclBiNs || + Tcl_Export(interp, itclBiNs, "*", /* resetListFirst */ 1) != TCL_OK) { + return TCL_ERROR; + } + + return TCL_OK; +} + + +/* + * ------------------------------------------------------------------------ + * Itcl_InstallBiMethods() + * + * Invoked when a class is first created, just after the class + * definition has been parsed, to add definitions for built-in + * methods to the class. If a method already exists in the class + * with the same name as the built-in, then the built-in is skipped. + * Otherwise, a method definition for the built-in method is added. + * + * Returns TCL_OK if successful, or TCL_ERROR (along with an error + * message in the interpreter) if anything goes wrong. + * ------------------------------------------------------------------------ + */ +int +Itcl_InstallBiMethods(interp, cdefn) + Tcl_Interp *interp; /* current interpreter */ + ItclClass *cdefn; /* class definition to be updated */ +{ + int result = TCL_OK; + Tcl_HashEntry *entry = NULL; + + int i; + ItclHierIter hier; + ItclClass *cdPtr; + + /* + * Scan through all of the built-in methods and see if + * that method already exists in the class. If not, add + * it in. + * + * TRICKY NOTE: The virtual tables haven't been built yet, + * so look for existing methods the hard way--by scanning + * through all classes. + */ + for (i=0; i < BiMethodListLen; i++) { + Itcl_InitHierIter(&hier, cdefn); + cdPtr = Itcl_AdvanceHierIter(&hier); + while (cdPtr) { + entry = Tcl_FindHashEntry(&cdPtr->functions, BiMethodList[i].name); + if (entry) { + break; + } + cdPtr = Itcl_AdvanceHierIter(&hier); + } + Itcl_DeleteHierIter(&hier); + + if (!entry) { + result = Itcl_CreateMethod(interp, cdefn, BiMethodList[i].name, + BiMethodList[i].usage, BiMethodList[i].registration); + + if (result != TCL_OK) { + break; + } + } + } + return result; +} + + +/* + * ------------------------------------------------------------------------ + * Itcl_BiIsaCmd() + * + * Invoked whenever the user issues the "isa" method for an object. + * Handles the following syntax: + * + * isa + * + * Checks to see if the object has the given anywhere + * in its heritage. Returns 1 if so, and 0 otherwise. + * ------------------------------------------------------------------------ + */ +/* ARGSUSED */ +int +Itcl_BiIsaCmd(clientData, interp, objc, objv) + ClientData clientData; /* class definition */ + Tcl_Interp *interp; /* current interpreter */ + int objc; /* number of arguments */ + Tcl_Obj *CONST objv[]; /* argument objects */ +{ + ItclClass *contextClass, *cdefn; + ItclObject *contextObj; + char *token; + + /* + * Make sure that this command is being invoked in the proper + * context. + */ + if (Itcl_GetContext(interp, &contextClass, &contextObj) != TCL_OK) { + return TCL_ERROR; + } + + if (!contextObj) { + Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), + "improper usage: should be \"object isa className\"", + (char*)NULL); + return TCL_ERROR; + } + if (objc != 2) { + token = Tcl_GetStringFromObj(objv[0], (int*)NULL); + Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), + "wrong # args: should be \"object ", token, " className\"", + (char*)NULL); + return TCL_ERROR; + } + + /* + * Look for the requested class. If it is not found, then + * try to autoload it. If it absolutely cannot be found, + * signal an error. + */ + token = Tcl_GetStringFromObj(objv[1], (int*)NULL); + cdefn = Itcl_FindClass(interp, token, /* autoload */ 1); + if (cdefn == NULL) { + return TCL_ERROR; + } + + if (Itcl_ObjectIsa(contextObj, cdefn)) { + Tcl_SetIntObj(Tcl_GetObjResult(interp), 1); + } else { + Tcl_SetIntObj(Tcl_GetObjResult(interp), 0); + } + return TCL_OK; +} + + +/* + * ------------------------------------------------------------------------ + * Itcl_BiConfigureCmd() + * + * Invoked whenever the user issues the "configure" method for an object. + * Handles the following syntax: + * + * configure ?-
itcl_bicmds.c Property changes : Added: svn:executable ## -0,0 +1 ## +* \ No newline at end of property

powered by: WebSVN 2.1.0

© copyright 1999-2024 OpenCores.org, equivalent to Oliscience, all rights reserved. OpenCores®, registered trademark.