OpenFPGA/libs/EXTERNAL/tcl8.6.12/pkgs/itcl4.2.2/generic/itclEnsemble.c

2239 lines
69 KiB
C
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* ------------------------------------------------------------------------
* 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
*
* overhauled version author: Arnulf Wiedemann
* ========================================================================
* 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"
#define ITCL_ENSEMBLE_CUSTOM 0x01
#define ITCL_ENSEMBLE_ENSEMBLE 0x02
/*
* Data used to represent an ensemble:
*/
struct Ensemble;
typedef struct EnsemblePart {
char *name; /* name of this part */
Tcl_Obj *namePtr;
Tcl_Command cmdPtr; /* command handling this part */
char *usage; /* usage string describing syntax */
struct Ensemble* ensemble; /* ensemble containing this part */
ItclArgList *arglistPtr; /* the parsed argument list */
Tcl_ObjCmdProc *objProc; /* handling procedure for part */
void *clientData; /* the procPtr for the part */
Tcl_CmdDeleteProc *deleteProc;
/* procedure used to destroy client data */
int minChars; /* chars needed to uniquely identify part */
int flags;
Tcl_Interp *interp;
Tcl_Obj *mapNamePtr;
Tcl_Obj *subEnsemblePtr;
Tcl_Obj *newMapDict;
} EnsemblePart;
#define ENSEMBLE_DELETE_STARTED 0x1
#define ENSEMBLE_PART_DELETE_STARTED 0x2
/*
* 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 */
int ensembleId; /* this ensembles id */
Tcl_Command cmdPtr; /* command representing this ensemble */
EnsemblePart* parent; /* parent part for sub-ensembles
* NULL => toplevel ensemble */
Tcl_Namespace *nsPtr; /* namespace for ensemble part commands */
int flags;
Tcl_Obj *namePtr;
} Ensemble;
/*
* Data shared by ensemble access commands and ensemble parser:
*/
typedef struct EnsembleParser {
Tcl_Interp* interp; /* interpreter containing ensembles */
Tcl_Interp* parser; /* child interp for parsing */
Ensemble* ensData; /* add parts to this ensemble */
} EnsembleParser;
static int EnsembleSubCmd(ClientData clientData, Tcl_Interp *interp,
int objc, Tcl_Obj *const objv[]);
static int EnsembleUnknownCmd(ClientData dummy, Tcl_Interp *interp,
int objc, Tcl_Obj *const objv[]);
/*
* Forward declarations for the procedures used in this file.
*/
static void GetEnsembleUsage (Tcl_Interp *interp,
Ensemble *ensData, Tcl_Obj *objPtr);
static void GetEnsemblePartUsage (Tcl_Interp *interp,
Ensemble *ensData, EnsemblePart *ensPart, Tcl_Obj *objPtr);
static int CreateEnsemble (Tcl_Interp *interp,
Ensemble *parentEnsData, const char *ensName);
static int AddEnsemblePart (Tcl_Interp *interp,
Ensemble* ensData, const char* partName, const char* usageInfo,
Tcl_ObjCmdProc *objProc, ClientData clientData,
Tcl_CmdDeleteProc *deleteProc, int flags, EnsemblePart **rVal);
static int FindEnsemble (Tcl_Interp *interp, const char **nameArgv,
int nameArgc, Ensemble** ensDataPtr);
static int CreateEnsemblePart (Tcl_Interp *interp,
Ensemble *ensData, const char* partName, EnsemblePart **ensPartPtr);
static void DeleteEnsemblePart (ClientData clientData);
static int FindEnsemblePart (Tcl_Interp *interp,
Ensemble *ensData, const char* partName, EnsemblePart **rensPart);
static void DeleteEnsemble(ClientData clientData);
static int FindEnsemblePartIndex (Ensemble *ensData,
const char *partName, int *posPtr);
static void ComputeMinChars (Ensemble *ensData, int pos);
static EnsembleParser* GetEnsembleParser (Tcl_Interp *interp);
static void DeleteEnsParser (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(
Tcl_Interp *interp) /* interpreter being initialized */
{
Tcl_DString buffer;
ItclObjectInfo *infoPtr;
infoPtr = (ItclObjectInfo *)Tcl_GetAssocData(interp, ITCL_INTERP_DATA, NULL);
Tcl_CreateObjCommand(interp, "::itcl::ensemble",
Itcl_EnsembleCmd, NULL, NULL);
Tcl_DStringInit(&buffer);
Tcl_DStringAppend(&buffer, ITCL_COMMANDS_NAMESPACE, -1);
Tcl_DStringAppend(&buffer, "::ensembles", -1);
infoPtr->ensembleInfo->ensembleNsPtr = Tcl_CreateNamespace(interp,
Tcl_DStringValue(&buffer), NULL, NULL);
Tcl_DStringFree(&buffer);
if (infoPtr->ensembleInfo->ensembleNsPtr == NULL) {
Tcl_AppendResult(interp, "error in creating namespace: ",
Tcl_DStringValue(&buffer), NULL);
return TCL_ERROR;
}
Tcl_CreateObjCommand(interp,
ITCL_COMMANDS_NAMESPACE "::ensembles::unknown",
EnsembleUnknownCmd, NULL, 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(
Tcl_Interp *interp, /* interpreter to be updated */
const char* ensName) /* name of the new ensemble */
{
const char **nameArgv = NULL;
int nameArgc;
Ensemble *parentEnsData;
/*
* Split the ensemble name into its path components.
*/
if (Tcl_SplitList(interp, (const char *)ensName, &nameArgc,
&nameArgv) != TCL_OK) {
goto ensCreateFail;
}
if (nameArgc < 1) {
Tcl_AppendStringsToObj(Tcl_GetObjResult(interp),
"invalid ensemble name \"", ensName, "\"",
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, "\"",
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_AppendObjToErrorInfo(interp, Tcl_ObjPrintf(
"\n (while creating ensemble \"%s\")",
ensName));
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(
Tcl_Interp *interp, /* interpreter to be updated */
const char* ensName, /* ensemble containing this part */
const char* partName, /* name of the new part */
const 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 */
{
const char **nameArgv = NULL;
int nameArgc;
Ensemble *ensData;
EnsemblePart *ensPart;
/*
* Parse the ensemble name and look for a containing ensemble.
*/
if (Tcl_SplitList(interp, (const char *)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, "\"",
NULL);
ckfree(pname);
goto ensPartFail;
}
/*
* Install the new part into the part list.
*/
if (AddEnsemblePart(interp, ensData, partName, usageInfo,
objProc, clientData, deleteProc, ITCL_ENSEMBLE_CUSTOM,
&ensPart) != TCL_OK) {
goto ensPartFail;
}
ckfree((char*)nameArgv);
return TCL_OK;
ensPartFail:
if (nameArgv) {
ckfree((char*)nameArgv);
}
Tcl_AppendObjToErrorInfo(interp, Tcl_ObjPrintf(
"\n (while adding to ensemble \"%s\")",
ensName));
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(
Tcl_Interp *interp, /* interpreter to be updated */
const char *ensName, /* ensemble containing the part */
const char *partName, /* name of the desired part */
Tcl_CmdInfo *infoPtr) /* returns: info associated with part */
{
const char **nameArgv = NULL;
int nameArgc;
Ensemble *ensData;
EnsemblePart *ensPart;
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, (const char *)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;
}
if (Tcl_GetCommandInfoFromToken(ensPart->cmdPtr, infoPtr) != 1) {
goto ensGetFail;
}
Itcl_DiscardInterpState(state);
ckfree((char *)nameArgv);
return 1;
ensGetFail:
if (nameArgv) {
ckfree((char *)nameArgv);
}
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(
Tcl_CmdInfo* infoPtr) /* command info from Tcl_GetCommandInfo() */
{
if (infoPtr) {
/* FIXME use CMD and Tcl_IsEnsemble!! */
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(
Tcl_Interp *interp, /* interpreter containing the ensemble */
const char *ensName, /* name of the ensemble */
Tcl_Obj *objPtr) /* returns: summary of usage info */
{
const 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, (const char *)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(interp, ensData, objPtr);
Itcl_DiscardInterpState(state);
ckfree((char *)nameArgv);
return 1;
ensUsageFail:
if (nameArgv) {
ckfree((char *)nameArgv);
}
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(
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;
Tcl_CmdInfo infoPtr;
/*
* If the argument is an ensemble part, then follow the chain
* back to the command word for the entire ensemble.
*/
chainObj = ensObjPtr;
if (chainObj) {
cmd = Tcl_GetCommandFromObj(interp, chainObj);
if (Tcl_GetCommandInfoFromToken(cmd, &infoPtr) != 1) {
return 0;
}
if (infoPtr.deleteProc == DeleteEnsemble) {
ensData = (Ensemble*)infoPtr.objClientData;
GetEnsembleUsage(interp, 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(
Tcl_Interp *interp,
Ensemble *ensData, /* ensemble data */
Tcl_Obj *objPtr) /* returns: summary of usage info */
{
const 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 {
if ((*ensPart->name == '@') &&
(strcmp(ensPart->name,"@itcl-builtin_info") == 0)) {
/* the builtin info command is not reported in [incr tcl] */
continue;
}
Tcl_AppendToObj(objPtr, spaces, -1);
GetEnsemblePartUsage(interp, ensData, 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(
Tcl_Interp *interp,
Ensemble *ensData,
EnsemblePart *ensPart, /* ensemble part for usage info */
Tcl_Obj *objPtr) /* returns: usage information */
{
EnsemblePart *part;
Tcl_Command cmdPtr;
const 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, part);
}
while (ensData->parent != NULL) {
ensData = ensData->parent->ensemble;
}
cmdPtr = ensData->cmdPtr;
name = Tcl_GetCommandName(interp, cmdPtr);
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);
} else {
/*
* If the part is itself an ensemble, summarize its usage.
*/
if (ensPart->cmdPtr != NULL) {
if (Tcl_IsEnsemble(ensPart->cmdPtr)) {
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(
Tcl_Interp *interp, /* interpreter to be updated */
Ensemble *parentEnsData, /* parent ensemble or NULL */
const char *ensName) /* name of the new ensemble */
{
Tcl_Obj *objPtr;
Tcl_DString buffer;
Tcl_HashEntry *hPtr;
Tcl_Obj *mapDict;
Tcl_Obj *toObjPtr;
ItclObjectInfo *infoPtr;
Ensemble *ensData;
EnsemblePart *ensPart;
int result;
int isNew;
char buf[20];
Tcl_Obj *unkObjPtr;
/*
* Create the data associated with the ensemble.
*/
infoPtr = (ItclObjectInfo *)Tcl_GetAssocData(interp, ITCL_INTERP_DATA, NULL);
infoPtr->ensembleInfo->numEnsembles++;
ensData = (Ensemble*)ckalloc(sizeof(Ensemble));
memset(ensData, 0, sizeof(Ensemble));
ensData->namePtr = Tcl_NewStringObj(ensName, -1);
Tcl_IncrRefCount(ensData->namePtr);
ensData->interp = interp;
ensData->numParts = 0;
ensData->maxParts = 10;
ensData->ensembleId = infoPtr->ensembleInfo->numEnsembles;
ensData->parts = (EnsemblePart**)ckalloc(
(unsigned)(ensData->maxParts*sizeof(EnsemblePart*))
);
memset(ensData->parts, 0, ensData->maxParts*sizeof(EnsemblePart*));
Tcl_DStringInit(&buffer);
Tcl_DStringAppend(&buffer, ITCL_COMMANDS_NAMESPACE "::ensembles::", -1);
sprintf(buf, "%d", ensData->ensembleId);
Tcl_DStringAppend(&buffer, buf, -1);
ensData->nsPtr = Tcl_CreateNamespace(interp, Tcl_DStringValue(&buffer),
ensData, DeleteEnsemble);
if (ensData->nsPtr == NULL) {
Tcl_AppendResult(interp, "error in creating namespace: ",
Tcl_DStringValue(&buffer), NULL);
result = TCL_ERROR;
goto finish;
}
/*
* If there is no parent data, then this is a top-level
* ensemble. Create the ensemble by installing its access
* command.
*/
if (parentEnsData == NULL) {
Tcl_Obj *unkObjPtr;
ensData->cmdPtr = Tcl_CreateEnsemble(interp, ensName,
Tcl_GetCurrentNamespace(interp), TCL_ENSEMBLE_PREFIX);
hPtr = Tcl_CreateHashEntry(&infoPtr->ensembleInfo->ensembles,
(char *)ensData->cmdPtr, &isNew);
if (!isNew) {
result = TCL_ERROR;
goto finish;
}
Tcl_SetHashValue(hPtr, ensData);
unkObjPtr = Tcl_NewStringObj(ITCL_COMMANDS_NAMESPACE, -1);
Tcl_AppendToObj(unkObjPtr, "::ensembles::unknown", -1);
if (Tcl_SetEnsembleUnknownHandler(NULL, ensData->cmdPtr,
unkObjPtr) != TCL_OK) {
Tcl_DecrRefCount(unkObjPtr);
result = TCL_ERROR;
goto finish;
}
Tcl_SetObjResult(interp, Tcl_NewStringObj(Tcl_DStringValue(&buffer), -1));
result = TCL_OK;
goto finish;
}
/*
* 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(ensData);
result = TCL_ERROR;
goto finish;
}
Tcl_DStringSetLength(&buffer, 0);
Tcl_DStringAppend(&buffer, infoPtr->ensembleInfo->ensembleNsPtr->fullName, -1);
Tcl_DStringAppend(&buffer, "::subensembles::", -1);
sprintf(buf, "%d", parentEnsData->ensembleId);
Tcl_DStringAppend(&buffer, buf, -1);
Tcl_DStringAppend(&buffer, "::", 2);
Tcl_DStringAppend(&buffer, ensName, -1);
objPtr = Tcl_NewStringObj(Tcl_DStringValue(&buffer), -1);
hPtr = Tcl_CreateHashEntry(&infoPtr->ensembleInfo->subEnsembles,
(char *)objPtr, &isNew);
if (isNew) {
Tcl_SetHashValue(hPtr, ensData);
}
ensPart->subEnsemblePtr = objPtr;
Tcl_IncrRefCount(ensPart->subEnsemblePtr);
ensPart->cmdPtr = Tcl_CreateEnsemble(interp, Tcl_DStringValue(&buffer),
Tcl_GetCurrentNamespace(interp), TCL_ENSEMBLE_PREFIX);
hPtr = Tcl_CreateHashEntry(&infoPtr->ensembleInfo->ensembles,
(char *)ensPart->cmdPtr, &isNew);
if (!isNew) {
result = TCL_ERROR;
goto finish;
}
Tcl_SetHashValue(hPtr, ensData);
unkObjPtr = Tcl_NewStringObj(ITCL_COMMANDS_NAMESPACE, -1);
Tcl_AppendToObj(unkObjPtr, "::ensembles::unknown", -1);
if (Tcl_SetEnsembleUnknownHandler(NULL, ensPart->cmdPtr,
unkObjPtr) != TCL_OK) {
result = TCL_ERROR;
goto finish;
}
Tcl_GetEnsembleMappingDict(NULL, parentEnsData->cmdPtr, &mapDict);
if (mapDict == NULL) {
mapDict = Tcl_NewObj();
}
toObjPtr = Tcl_NewStringObj(Tcl_DStringValue(&buffer), -1);
Tcl_DictObjPut(NULL, mapDict, ensData->namePtr, toObjPtr);
Tcl_SetEnsembleMappingDict(NULL, parentEnsData->cmdPtr, mapDict);
ensData->cmdPtr = ensPart->cmdPtr;
ensData->parent = ensPart;
result = TCL_OK;
finish:
Tcl_DStringFree(&buffer);
return result;
}
/*
*----------------------------------------------------------------------
*
* 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(
Tcl_Interp *interp, /* interpreter to be updated */
Ensemble* ensData, /* ensemble that will contain this part */
const char* partName, /* name of the new part */
const 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 */
int flags,
EnsemblePart **rVal) /* returns: new ensemble part */
{
Tcl_Obj *mapDict;
Tcl_Command cmd;
EnsemblePart *ensPart;
/*
* Install the new part into the part list.
*/
if (CreateEnsemblePart(interp, ensData, partName, &ensPart) != TCL_OK) {
return TCL_ERROR;
}
if (usageInfo) {
ensPart->usage = (char *)ckalloc(strlen(usageInfo)+1);
strcpy(ensPart->usage, usageInfo);
}
ensPart->objProc = objProc;
ensPart->clientData = clientData;
ensPart->deleteProc = deleteProc;
ensPart->flags = flags;
mapDict = NULL;
Tcl_GetEnsembleMappingDict(NULL, ensData->cmdPtr, &mapDict);
if (mapDict == NULL) {
mapDict = Tcl_NewObj();
ensPart->newMapDict = mapDict;
}
ensPart->mapNamePtr = Tcl_NewStringObj(ensData->nsPtr->fullName, -1);
Tcl_AppendToObj(ensPart->mapNamePtr, "::", 2);
Tcl_AppendToObj(ensPart->mapNamePtr, partName, -1);
Tcl_IncrRefCount(ensPart->namePtr);
Tcl_IncrRefCount(ensPart->mapNamePtr);
Tcl_DictObjPut(NULL, mapDict, ensPart->namePtr, ensPart->mapNamePtr);
cmd = Tcl_CreateObjCommand(interp, Tcl_GetString(ensPart->mapNamePtr),
EnsembleSubCmd, ensPart, DeleteEnsemblePart);
if (cmd == NULL) {
Tcl_DictObjRemove(NULL, mapDict, ensPart->namePtr);
Tcl_DecrRefCount(ensPart->namePtr);
Tcl_DecrRefCount(ensPart->mapNamePtr);
return TCL_ERROR;
}
Tcl_SetEnsembleMappingDict(interp, ensData->cmdPtr, mapDict);
*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) /* ensemble data */
{
FOREACH_HASH_DECLS;
ItclObjectInfo *infoPtr;
Ensemble* ensData;
Ensemble* ensData2;
ensData = (Ensemble*)clientData;
/* remove the unknown handler if set to release the Tcl_Obj of the name */
if (Tcl_FindCommand(ensData->interp, Tcl_GetString(ensData->namePtr),
NULL, 0) != NULL) {
Tcl_SetEnsembleUnknownHandler(NULL, ensData->cmdPtr, NULL);
}
/*
* 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]);
}
Tcl_DecrRefCount(ensData->namePtr);
ckfree((char*)ensData->parts);
ensData->parts = NULL;
ensData->numParts = 0;
infoPtr = (ItclObjectInfo *)Tcl_GetAssocData(ensData->interp, ITCL_INTERP_DATA, NULL);
FOREACH_HASH_VALUE(ensData2, &infoPtr->ensembleInfo->ensembles) {
if (ensData2 == ensData) {
Tcl_DeleteHashEntry(hPtr);
}
}
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(
Tcl_Interp *interp, /* interpreter containing the ensemble */
const char **nameArgv, /* path of names leading to ensemble */
int nameArgc, /* number of strings in nameArgv */
Ensemble** ensDataPtr) /* returns: ensemble data */
{
int i;
Tcl_Command cmdPtr;
Ensemble *ensData;
EnsemblePart *ensPart;
Tcl_Obj *objPtr;
Tcl_CmdInfo cmdInfo;
Tcl_HashEntry *hPtr;
ItclObjectInfo *infoPtr;
*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.
*/
objPtr = Tcl_NewStringObj(nameArgv[0], -1);
cmdPtr = Tcl_FindEnsemble(interp, objPtr, 0);
Tcl_DecrRefCount(objPtr);
if (cmdPtr == NULL) {
Tcl_AppendStringsToObj(Tcl_GetObjResult(interp),
"command \"", nameArgv[0], "\" is not an ensemble",
NULL);
return TCL_ERROR;
}
infoPtr = (ItclObjectInfo *)Tcl_GetAssocData(interp, ITCL_INTERP_DATA, NULL);
hPtr = Tcl_FindHashEntry(&infoPtr->ensembleInfo->ensembles, (char *)cmdPtr);
if (hPtr == NULL) {
Tcl_AppendStringsToObj(Tcl_GetObjResult(interp),
"command \"", nameArgv[0], "\" is not an ensemble",
NULL);
return TCL_ERROR;
}
ensData = (Ensemble *)Tcl_GetHashValue(hPtr);
/*
* 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, "\"",
NULL);
ckfree(pname);
return TCL_ERROR;
}
cmdPtr = ensPart->cmdPtr;
if (cmdPtr == NULL) {
Tcl_AppendStringsToObj(Tcl_GetObjResult(interp),
"part \"", nameArgv[i], "\" is not an ensemble",
NULL);
return TCL_ERROR;
}
if (!Tcl_IsEnsemble(cmdPtr)) {
Tcl_AppendStringsToObj(Tcl_GetObjResult(interp),
"part \"", nameArgv[i], "\" is not an ensemble",
NULL);
return TCL_ERROR;
}
if (Tcl_GetCommandInfoFromToken(cmdPtr, &cmdInfo) != 1) {
return TCL_ERROR;
}
ensData = (Ensemble*)cmdInfo.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(
Tcl_Interp *interp, /* interpreter containing the ensemble */
Ensemble *ensData, /* ensemble being modified */
const char* partName, /* name of the new part */
EnsemblePart **ensPartPtr) /* returns: new ensemble part */
{
int i;
int pos;
int size;
EnsemblePart** partList;
EnsemblePart* ensPart;
/*
* 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",
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(partList, 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++;
ensPart = (EnsemblePart*)ckalloc(sizeof(EnsemblePart));
memset(ensPart, 0, sizeof(EnsemblePart));
ensPart->name = (char*)ckalloc(strlen(partName)+1);
strcpy(ensPart->name, partName);
ensPart->namePtr = Tcl_NewStringObj(ensPart->name, -1);
ensPart->ensemble = ensData;
ensPart->interp = interp;
ensData->parts[pos] = ensPart;
/*
* 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 = ensPart;
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(
ClientData clientData) /* part being destroyed */
{
Tcl_Obj *mapDict;
Tcl_HashEntry *hPtr;
ItclObjectInfo *infoPtr;
Ensemble *ensData;
Ensemble *ensData2;
EnsemblePart *ensPart;
int i;
int pos;
mapDict = NULL;
ensPart = (EnsemblePart *)clientData;
if (ensPart == NULL) {
return;
}
ensData = ensPart->ensemble;
/*
* If this part has a delete proc, then call it to free
* up the client data.
*/
if ((ensPart->deleteProc != NULL) && (ensPart->clientData != NULL)) {
(*ensPart->deleteProc)(ensPart->clientData);
}
/* if it is a subensemble remove the command to free the data */
if (ensPart->subEnsemblePtr != NULL) {
infoPtr = (ItclObjectInfo *)Tcl_GetAssocData(ensData->interp, ITCL_INTERP_DATA, NULL);
hPtr = Tcl_FindHashEntry(&infoPtr->ensembleInfo->subEnsembles,
(char *)ensPart->subEnsemblePtr);
if (hPtr != NULL) {
ensData2 = (Ensemble *)Tcl_GetHashValue(hPtr);
Tcl_DeleteNamespace(ensData2->nsPtr);
Tcl_DeleteHashEntry(hPtr);
}
Tcl_SetEnsembleUnknownHandler(NULL, ensPart->cmdPtr, NULL);
hPtr = Tcl_FindHashEntry(&infoPtr->ensembleInfo->ensembles,
(char *)ensPart->ensemble->cmdPtr);
if (hPtr != NULL) {
ensData2 = (Ensemble *)Tcl_GetHashValue(hPtr);
Tcl_GetEnsembleMappingDict(NULL, ensData2->cmdPtr, &mapDict);
if (mapDict != NULL) {
Tcl_DictObjRemove(ensPart->interp, mapDict,
ensPart->namePtr);
Tcl_SetEnsembleMappingDict(NULL, ensData2->cmdPtr, mapDict);
}
}
Tcl_DecrRefCount(ensPart->subEnsemblePtr);
if (ensPart->newMapDict != NULL) {
Tcl_DecrRefCount(ensPart->newMapDict);
}
}
/*
* 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.
*/
mapDict = NULL;
if (Tcl_FindCommand(ensData->interp, Tcl_GetString(ensData->namePtr),
NULL, 0) != NULL) {
Tcl_GetEnsembleMappingDict(ensData->interp, ensData->cmdPtr, &mapDict);
if (mapDict != NULL) {
if (!Tcl_IsShared(mapDict)) {
Tcl_DictObjRemove(ensPart->interp, mapDict, ensPart->namePtr);
Tcl_SetEnsembleMappingDict(ensPart->interp, ensData->cmdPtr,
mapDict);
}
}
}
/* this is the map !!! */
if (ensPart->mapNamePtr != NULL) {
Tcl_DecrRefCount(ensPart->mapNamePtr);
}
Tcl_DecrRefCount(ensPart->namePtr);
if (ensPart->usage != NULL) {
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(
Tcl_Interp *interp, /* interpreter containing the ensemble */
Ensemble *ensData, /* ensemble being searched */
const 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(NULL, 0);
Tcl_AppendStringsToObj(resultPtr,
"ambiguous option \"", partName, "\": should be one of...",
NULL);
for (i=pos; i < ensData->numParts; i++) {
if (strncmp(partName, ensData->parts[i]->name, nlen) != 0) {
break;
}
Tcl_AppendToObj(resultPtr, "\n ", 3);
GetEnsemblePartUsage(interp, ensData, 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(
Ensemble *ensData, /* ensemble being searched */
const 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(
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;
}
}
/*
*----------------------------------------------------------------------
*
* Itcl_EnsembleCmd --
*
* Invoked by Tcl whenever the user issues the "ensemble"
* command to manipulate an ensemble. Handles the following
* syntax:
*
* ensemble <ensName> ?<command> <arg> <arg>...?
* ensemble <ensName> {
* part <partName> <args> <body>
* ensemble <ensName> {
* ...
* }
* }
*
* Finds or creates the ensemble <ensName>, 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 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;
Ensemble *savedEnsData;
EnsemblePart *ensPart;
Tcl_Command cmd;
Tcl_Obj *objPtr;
Tcl_HashEntry *hPtr;
ItclObjectInfo *infoPtr;
ItclShowArgs(1, "Itcl_EnsembleCmd", objc, objv);
/*
* Make sure that an ensemble name was specified.
*/
if (objc < 2) {
Tcl_AppendStringsToObj(Tcl_GetObjResult(interp),
"wrong # args: should be \"",
Tcl_GetString(objv[0]),
" name ?command arg arg...?\"",
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_GetString(objv[1]);
if (ensData) {
if (FindEnsemblePart(ensInfo->interp, ensData, ensName, &ensPart) != TCL_OK) {
ensPart = NULL;
}
if (ensPart == NULL) {
if (CreateEnsemble(ensInfo->interp, ensData, ensName) != TCL_OK) {
Tcl_TransferResult(ensInfo->interp, TCL_ERROR, interp);
return TCL_ERROR;
}
if (FindEnsemblePart(ensInfo->interp, ensData, ensName, &ensPart)
!= TCL_OK) {
Tcl_Panic("Itcl_EnsembleCmd: can't create ensemble");
}
}
cmd = ensPart->cmdPtr;
infoPtr = (ItclObjectInfo *)Tcl_GetAssocData(ensInfo->interp, ITCL_INTERP_DATA, NULL);
hPtr = Tcl_FindHashEntry(&infoPtr->ensembleInfo->ensembles,
(char *)ensPart->cmdPtr);
if (hPtr == NULL) {
Tcl_AppendStringsToObj(Tcl_GetObjResult(interp),
"part \"", Tcl_GetString(objv[1]),
"\" is not an ensemble",
NULL);
return TCL_ERROR;
}
ensData = (Ensemble *)Tcl_GetHashValue(hPtr);
} else {
/*
* Otherwise, the desired ensemble is a top-level ensemble.
* Find or create the access command for the ensemble, and
* then get its data.
*/
cmd = Tcl_FindCommand(interp, ensName, NULL, 0);
if (cmd == NULL) {
if (CreateEnsemble(interp, NULL, ensName)
!= TCL_OK) {
return TCL_ERROR;
}
cmd = Tcl_FindCommand(interp, ensName, NULL, 0);
}
if (cmd == NULL) {
Tcl_AppendStringsToObj(Tcl_GetObjResult(interp),
"command \"", Tcl_GetString(objv[1]),
"\" is not an ensemble",
NULL);
return TCL_ERROR;
}
infoPtr = (ItclObjectInfo *)Tcl_GetAssocData(interp, ITCL_INTERP_DATA, NULL);
hPtr = Tcl_FindHashEntry(&infoPtr->ensembleInfo->ensembles, (char *)cmd);
if (hPtr == NULL) {
Tcl_AppendStringsToObj(Tcl_GetObjResult(interp),
"command \"", Tcl_GetString(objv[1]),
"\" is not an ensemble",
NULL);
return TCL_ERROR;
}
ensData = (Ensemble *)Tcl_GetHashValue(hPtr);
}
/*
* 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) {
status = Tcl_EvalObjEx(ensInfo->parser, objv[2], 0);
} else {
if (objc > 3) {
objPtr = Tcl_NewListObj(objc-2, objv+2);
Tcl_IncrRefCount(objPtr); /* stop Eval trashing it */
status = Tcl_EvalObjEx(ensInfo->parser, objPtr, 0);
Tcl_DecrRefCount(objPtr); /* we're done with the object */
}
}
/*
* Copy the result from the parser interpreter to the
* parent 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) {
/* no longer needed, no extra interpreter !! */
const char *errInfo = Tcl_GetVar2(ensInfo->parser, "::errorInfo",
NULL, TCL_GLOBAL_ONLY);
if (errInfo) {
Tcl_AppendObjToErrorInfo(interp, Tcl_NewStringObj(errInfo, -1));
}
if (objc == 3) {
Tcl_AppendObjToErrorInfo(interp, Tcl_ObjPrintf(
"\n (\"ensemble\" body line %d)",
Tcl_GetErrorLine(ensInfo->parser)));
}
}
Tcl_SetObjResult(interp, Tcl_GetObjResult(ensInfo->parser));
ensInfo->ensData = savedEnsData;
return status;
}
/*
*----------------------------------------------------------------------
*
* GetEnsembleParser --
*
* Returns the child 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(
Tcl_Interp *interp) /* interpreter handling the ensemble */
{
EnsembleParser *ensInfo;
/*
* 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 child interpreter that can be used to parse
* the body of an ensemble definition.
*/
ensInfo = (EnsembleParser*)ckalloc(sizeof(EnsembleParser));
ensInfo->interp = interp;
ensInfo->parser = Tcl_CreateInterp();
ensInfo->ensData = NULL;
Tcl_DeleteNamespace(Tcl_GetGlobalNamespace(ensInfo->parser));
/*
* Add the allowed commands to the parser interpreter:
* part, delete, ensemble
*/
Tcl_CreateObjCommand(ensInfo->parser, "part", Itcl_EnsPartCmd,
ensInfo, NULL);
Tcl_CreateObjCommand(ensInfo->parser, "option", Itcl_EnsPartCmd,
ensInfo, NULL);
Tcl_CreateObjCommand(ensInfo->parser, "ensemble", Itcl_EnsembleCmd,
ensInfo, NULL);
/*
* Install the parser data, so we'll have it the next time
* we call this procedure.
*/
(void) Tcl_SetAssocData(interp, "itcl_ensembleParser",
DeleteEnsParser, ensInfo);
return ensInfo;
}
/*
*----------------------------------------------------------------------
*
* DeleteEnsParser --
*
* Called when an interpreter is destroyed to clean up the
* ensemble parser within it. Destroys the child interpreter
* and frees up the data associated with it.
*
* Results:
* None.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
/* ARGSUSED */
static void
DeleteEnsParser(
ClientData clientData, /* client data for ensemble-related commands */
Tcl_Interp *dummy) /* interpreter containing the data */
{
EnsembleParser* ensInfo = (EnsembleParser*)clientData;
(void)dummy;
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 <ensName> {
* part <partName> <args> <body>
* }
*
* Adds a new part called <partName> 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 <args> and a <body> 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 clientData, /* ensemble data */
Tcl_Interp *interp, /* current interpreter */
int objc, /* number of arguments */
Tcl_Obj *const objv[]) /* argument objects */
{
Tcl_Obj *usagePtr;
Tcl_Proc procPtr;
EnsembleParser *ensInfo = (EnsembleParser*)clientData;
Ensemble *ensData = (Ensemble*)ensInfo->ensData;
EnsemblePart *ensPart;
ItclArgList *arglistPtr;
char *partName;
char *usage;
int result;
int argc;
int maxArgc;
Tcl_CmdInfo cmdInfo;
ItclShowArgs(1, "Itcl_EnsPartCmd", objc, objv);
if (objc != 4) {
Tcl_AppendStringsToObj(Tcl_GetObjResult(interp),
"wrong # args: should be \"",
Tcl_GetString(objv[0]),
" name args body\"",
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_GetString(objv[1]);
if (ItclCreateArgList(interp, Tcl_GetString(objv[2]), &argc, &maxArgc,
&usagePtr, &arglistPtr, NULL, partName) != TCL_OK) {
result = TCL_ERROR;
goto errorOut;
}
if (Tcl_GetCommandInfoFromToken(ensData->cmdPtr, &cmdInfo) != 1) {
result = TCL_ERROR;
goto errorOut;
}
if (Tcl_CreateProc(ensInfo->interp, cmdInfo.namespacePtr, partName, objv[2], objv[3],
&procPtr) != TCL_OK) {
Tcl_TransferResult(ensInfo->interp, TCL_ERROR, interp);
result = TCL_ERROR;
goto errorOut;
}
usage = Tcl_GetString(usagePtr);
/*
* 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.
*/
result = AddEnsemblePart(ensInfo->interp, ensData, partName, usage,
(Tcl_ObjCmdProc *)Tcl_GetObjInterpProc(), procPtr, _Tcl_ProcDeleteProc,
ITCL_ENSEMBLE_ENSEMBLE, &ensPart);
if (result == TCL_ERROR) {
_Tcl_ProcDeleteProc(procPtr);
}
Tcl_TransferResult(ensInfo->interp, result, interp);
errorOut:
Tcl_DecrRefCount(usagePtr);
ItclDeleteArgList(arglistPtr);
return result;
}
/*
*----------------------------------------------------------------------
*
* 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 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;
(void)objc;
cmdName = Tcl_GetString(objv[0]);
objPtr = Tcl_NewStringObj(NULL, 0);
Tcl_AppendStringsToObj(objPtr,
"bad option \"", cmdName, "\": should be one of...\n",
NULL);
GetEnsembleUsage(interp, ensData, objPtr);
Tcl_SetObjResult(interp, objPtr);
return TCL_ERROR;
}
/*
*----------------------------------------------------------------------
*
* EnsembleSubCmd --
*
*----------------------------------------------------------------------
*/
static int
CallInvokeEnsembleMethod(
ClientData data[],
Tcl_Interp *interp,
int result)
{
Tcl_Namespace *nsPtr = (Tcl_Namespace *)data[0];
EnsemblePart *ensPart = (EnsemblePart *)data[1];
int objc = PTR2INT(data[2]);
Tcl_Obj *const *objv = (Tcl_Obj *const *)data[3];
result = Itcl_InvokeEnsembleMethod(interp, nsPtr, ensPart->namePtr,
(Tcl_Proc *)ensPart->clientData, objc, objv);
return result;
}
static int
CallInvokeEnsembleMethod2(
ClientData data[],
Tcl_Interp *interp,
int result)
{
EnsemblePart *ensPart = (EnsemblePart *)data[0];
int objc = PTR2INT(data[1]);
Tcl_Obj *const*objv = (Tcl_Obj *const*)data[2];
result = (*ensPart->objProc)(ensPart->clientData, interp, objc, objv);
return result;
}
static int
EnsembleSubCmd(
ClientData clientData, /* ensPart struct pointer */
Tcl_Interp *interp, /* Current interpreter. */
int objc, /* Number of arguments. */
Tcl_Obj *const objv[]) /* Argument objects. */
{
int result;
Tcl_Namespace *nsPtr;
EnsemblePart *ensPart;
void *callbackPtr;
ItclShowArgs(1, "EnsembleSubCmd", objc, objv);
result = TCL_OK;
ensPart = (EnsemblePart *)clientData;
nsPtr = Tcl_GetCurrentNamespace(interp);
callbackPtr = Itcl_GetCurrentCallbackPtr(interp);
if (ensPart->flags & ITCL_ENSEMBLE_ENSEMBLE) {
/* FIXME !!! */
if (ensPart->clientData == NULL) {
return TCL_ERROR;
}
Tcl_NRAddCallback(interp, CallInvokeEnsembleMethod, nsPtr, ensPart, INT2PTR(objc), (void *)objv);
} else {
Tcl_NRAddCallback(interp, CallInvokeEnsembleMethod2, ensPart, INT2PTR(objc), (void *)objv, NULL);
}
result = Itcl_NRRunCallbacks(interp, callbackPtr);
return result;
}
/*
* ------------------------------------------------------------------------
* EnsembleUnknownCmd()
*
* the unknown handler for the ensemble commands
* ------------------------------------------------------------------------
*/
/* ARGSUSED */
static int
EnsembleUnknownCmd(
ClientData dummy, /* not used */
Tcl_Interp *interp, /* current interpreter */
int objc, /* number of arguments */
Tcl_Obj *const objv[]) /* argument objects */
{
Tcl_Command cmd;
Tcl_HashEntry *hPtr;
ItclObjectInfo *infoPtr;
EnsemblePart *ensPart;
Ensemble *ensData;
(void)dummy;
ItclShowArgs(2, "EnsembleUnknownCmd", objc, objv);
cmd = Tcl_GetCommandFromObj(interp, objv[1]);
if (cmd == NULL) {
Tcl_AppendResult(interp, "EnsembleUnknownCmd, ensemble not found!",
Tcl_GetString(objv[1]), NULL);
return TCL_ERROR;
}
infoPtr = (ItclObjectInfo *)Tcl_GetAssocData(interp, ITCL_INTERP_DATA, NULL);
hPtr = Tcl_FindHashEntry(&infoPtr->ensembleInfo->ensembles, (char *)cmd);
if (hPtr == NULL) {
Tcl_AppendResult(interp, "EnsembleUnknownCmd, ensemble struct not ",
"found!", Tcl_GetString(objv[1]), NULL);
return TCL_ERROR;
}
ensData = (Ensemble *)Tcl_GetHashValue(hPtr);
if (objc < 3) {
/* produce usage message */
Tcl_Obj *objPtr = Tcl_NewStringObj(
"wrong # args: should be one of...\n", -1);
GetEnsembleUsage(interp, ensData, objPtr);
Tcl_SetObjResult(interp, objPtr);
return TCL_ERROR;
}
if (FindEnsemblePart(interp, ensData, "@error", &ensPart) != TCL_OK) {
Tcl_AppendResult(interp, "FindEnsemblePart error", NULL);
return TCL_ERROR;
}
if (ensPart != NULL) {
Tcl_Obj *listPtr;
listPtr = Tcl_NewListObj(0, NULL);
Tcl_ListObjAppendElement(NULL, listPtr, objv[1]);
Tcl_ListObjAppendElement(NULL, listPtr, Tcl_NewStringObj("@error", -1));
Tcl_ListObjAppendElement(NULL, listPtr, objv[2]);
Tcl_SetObjResult(interp, listPtr);
return TCL_OK;
}
return Itcl_EnsembleErrorCmd(ensData, interp, objc-2, objv+2);
}
/*
*----------------------------------------------------------------------
*
* Itcl_EnsembleDeleteCmd --
*
* Invoked when the user tries to delet an ensemble
*----------------------------------------------------------------------
*/
int
Itcl_EnsembleDeleteCmd(
ClientData clientData, /* infoPtr */
Tcl_Interp *interp, /* current interpreter */
int objc, /* number of arguments */
Tcl_Obj *const objv[]) /* argument objects */
{
Tcl_HashEntry *hPtr;
Tcl_Command cmdPtr;
Ensemble *ensData;
ItclObjectInfo *infoPtr;
int i;
infoPtr = (ItclObjectInfo *)clientData;
ItclShowArgs(1, "Itcl_EnsembleDeleteCmd", objc, objv);
for (i = 1; i < objc; i++) {
cmdPtr = Tcl_FindCommand(interp, Tcl_GetString(objv[i]), NULL, 0);
if (cmdPtr == NULL) {
Tcl_AppendResult(interp, "no such ensemble \"",
Tcl_GetString(objv[i]), "\"", NULL);
return TCL_ERROR;
}
hPtr = Tcl_FindHashEntry(&infoPtr->ensembleInfo->ensembles, (char *)cmdPtr);
if (hPtr == NULL) {
Tcl_AppendResult(interp, "no such ensemble \"",
Tcl_GetString(objv[i]), "\"", NULL);
return TCL_ERROR;
}
ensData = (Ensemble *)Tcl_GetHashValue(hPtr);
Itcl_RenameCommand(ensData->interp, Tcl_GetString(ensData->namePtr), "");
if (Tcl_FindNamespace(interp, ensData->nsPtr->fullName, NULL, 0)
!= NULL) {
Tcl_DeleteNamespace(ensData->nsPtr);
}
}
return TCL_OK;
}
/*
*----------------------------------------------------------------------
*
* Itcl_FinishEnsemble --
*
* Invoked when itcl package is finished or ItclFinishCmd is called
*----------------------------------------------------------------------
*/
void
ItclFinishEnsemble(
ItclObjectInfo *infoPtr)
{
Tcl_DeleteAssocData(infoPtr->interp, "itcl_ensembleParser");
}