diff options
Diffstat (limited to 'itcl/itcl/generic/itcl_ensemble.c')
-rw-r--r-- | itcl/itcl/generic/itcl_ensemble.c | 2248 |
1 files changed, 2248 insertions, 0 deletions
diff --git a/itcl/itcl/generic/itcl_ensemble.c b/itcl/itcl/generic/itcl_ensemble.c new file mode 100644 index 00000000000..60ba06ebab7 --- /dev/null +++ b/itcl/itcl/generic/itcl_ensemble.c @@ -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$ + * ======================================================================== + * 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: + * + * <ensembleName> <partName> ?<arg> <arg>...? + * + * Looks for the <partName> 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 <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, 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 <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, 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; +} |