summaryrefslogtreecommitdiffstats
path: root/generic/tclNamesp.c
diff options
context:
space:
mode:
authordkf <donal.k.fellows@manchester.ac.uk>2003-09-29 14:37:13 (GMT)
committerdkf <donal.k.fellows@manchester.ac.uk>2003-09-29 14:37:13 (GMT)
commitda7765230338186675e0f6ccbfba67efa4b88625 (patch)
treef06c23ff0f1c69d9401df1b4a24919018fc717a6 /generic/tclNamesp.c
parentc5c73ec317fce63210aedd53ebda27ebef52bcc3 (diff)
downloadtcl-da7765230338186675e0f6ccbfba67efa4b88625.zip
tcl-da7765230338186675e0f6ccbfba67efa4b88625.tar.gz
tcl-da7765230338186675e0f6ccbfba67efa4b88625.tar.bz2
TIP#112 ([namespace ensemble] command) implementation.
Diffstat (limited to 'generic/tclNamesp.c')
-rw-r--r--generic/tclNamesp.c1606
1 files changed, 1598 insertions, 8 deletions
diff --git a/generic/tclNamesp.c b/generic/tclNamesp.c
index 6961755..b09e5f2 100644
--- a/generic/tclNamesp.c
+++ b/generic/tclNamesp.c
@@ -5,11 +5,13 @@
* commands and global variables. The global :: namespace is the
* traditional Tcl "global" scope. Other namespaces are created as
* children of the global namespace. These other namespaces contain
- * special-purpose commands and variables for packages.
+ * special-purpose commands and variables for packages. Also includes
+ * the TIP#112 ensemble machinery.
*
* Copyright (c) 1993-1997 Lucent Technologies.
* Copyright (c) 1997 Sun Microsystems, Inc.
* Copyright (c) 1998-1999 by Scriptics Corporation.
+ * Copyright (c) 2002-2003 Donal K. Fellows.
*
* Originally implemented by
* Michael J. McLennan
@@ -19,7 +21,7 @@
* See the file "license.terms" for information on usage and redistribution
* of this file, and for a DISCLAIMER OF ALL WARRANTIES.
*
- * RCS: @(#) $Id: tclNamesp.c,v 1.32 2003/06/18 18:30:01 msofer Exp $
+ * RCS: @(#) $Id: tclNamesp.c,v 1.33 2003/09/29 14:37:14 dkf Exp $
*/
#include "tclInt.h"
@@ -74,15 +76,110 @@ typedef struct ResolvedNsName {
} ResolvedNsName;
/*
+ * The client data for an ensemble command. This consists of the
+ * table of commands that are actually exported by the namespace, and
+ * an epoch counter that, combined with the exportLookupEpoch field of
+ * the namespace structure, defines whether the table contains valid
+ * data or will need to be recomputed next time the ensemble command
+ * is called.
+ */
+
+typedef struct EnsembleConfig {
+ Namespace *nsPtr; /* The namspace backing this ensemble up. */
+ Tcl_Command token; /* The token for the command that provides
+ * ensemble support for the namespace, or
+ * NULL if the command has been deleted (or
+ * never existed; the global namespace never
+ * has an ensemble command.) */
+ int epoch; /* The epoch at which this ensemble's table of
+ * exported commands is valid. */
+ char **subcommandArrayPtr; /* Array of ensemble subcommand names. At all
+ * consistent points, this will have the same
+ * number of entries as there are entries in
+ * the subcommandTable hash. */
+ Tcl_HashTable subcommandTable;
+ /* Hash table of ensemble subcommand names,
+ * which are its keys so this also provides
+ * the storage management for those subcommand
+ * names. The contents of the entry values are
+ * object version the prefix lists to use when
+ * substituting for the command/subcommand to
+ * build the ensemble implementation command.
+ * Has to be stored here as well as in
+ * subcommandDict because that field is NULL
+ * when we are deriving the ensemble from the
+ * namespace exports list.
+ * FUTURE WORK: use object hash table here. */
+ struct EnsembleConfig *next;/* The next ensemble in the linked list of
+ * ensembles associated with a namespace. If
+ * this field points to this ensemble, the
+ * structure has already been unlinked from
+ * all lists, and cannot be found by scanning
+ * the list from the namespace's ensemble
+ * field. */
+ int flags; /* ORed combo of ENS_DEAD and ENS_PREFIX. */
+
+ /* OBJECT FIELDS FOR ENSEMBLE CONFIGURATION */
+
+ Tcl_Obj *subcommandDict; /* Dictionary providing mapping from
+ * subcommands to their implementing command
+ * prefixes, or NULL if we are to build the
+ * map automatically from the namespace
+ * exports. */
+ Tcl_Obj *subcmdList; /* List of commands that this ensemble
+ * actually provides, and whose implementation
+ * will be built using the subcommandDict (if
+ * present and defined) and by simple mapping
+ * to the namespace otherwise. If NULL,
+ * indicates that we are using the (dynamic)
+ * list of currently exported commands. */
+ Tcl_Obj *unknownHandler; /* Script prefix used to handle the case when
+ * no match is found (according to the rule
+ * defined by flag bit ENS_PREFIX) or NULL to
+ * use the default error-generating behaviour.
+ * The script execution gets all the arguments
+ * to the ensemble command (including objv[0])
+ * and will have the results passed directly
+ * back to the caller (including the error
+ * code) unless the code is TCL_CONTINUE in
+ * which case the subcommand will be reparsed
+ * by the ensemble core, presumably because
+ * the ensemble itself has been updated. */
+} EnsembleConfig;
+
+#define ENS_DEAD 0x1 /* Flag value to say that the ensemble is dead
+ * and on its way out. */
+#define ENS_PREFIX 0x2 /* Flag value to say whether to allow
+ * unambiguous prefixes of commands or to
+ * require exact matches for command names. */
+
+/*
+ * The data cached in a subcommand's Tcl_Obj rep. This structure is
+ * not shared between Tcl_Objs referring to the same subcommand, even
+ * where one is a duplicate of another.
+ */
+
+typedef struct EnsembleCmdRep {
+ Namespace *nsPtr; /* The namespace backing the ensemble which
+ * this is a subcommand of. */
+ int epoch; /* Used to confirm when the data in this
+ * really structure matches up with the
+ * ensemble. */
+ char *fullSubcmdName; /* The full (local) name of the subcommand,
+ * allocated with ckalloc(). */
+ Tcl_Obj *realPrefixObj; /* Object containing the prefix words of the
+ * command that implements this ensemble
+ * subcommand. */
+} EnsembleCmdRep;
+
+/*
* Declarations for procedures local to this file:
*/
-static void DeleteImportedCmd _ANSI_ARGS_((
- ClientData clientData));
+static void DeleteImportedCmd _ANSI_ARGS_((ClientData clientData));
static void DupNsNameInternalRep _ANSI_ARGS_((Tcl_Obj *objPtr,
Tcl_Obj *copyPtr));
-static void FreeNsNameInternalRep _ANSI_ARGS_((
- Tcl_Obj *objPtr));
+static void FreeNsNameInternalRep _ANSI_ARGS_((Tcl_Obj *objPtr));
static int GetNamespaceFromObj _ANSI_ARGS_((
Tcl_Interp *interp, Tcl_Obj *objPtr,
Tcl_Namespace **nsPtrPtr));
@@ -101,6 +198,9 @@ static int NamespaceCurrentCmd _ANSI_ARGS_((
static int NamespaceDeleteCmd _ANSI_ARGS_((
ClientData dummy, Tcl_Interp *interp,
int objc, Tcl_Obj *CONST objv[]));
+static int NamespaceEnsembleCmd _ANSI_ARGS_((
+ ClientData dummy, Tcl_Interp *interp,
+ int objc, Tcl_Obj *CONST objv[]));
static int NamespaceEvalCmd _ANSI_ARGS_((
ClientData dummy, Tcl_Interp *interp,
int objc, Tcl_Obj *CONST objv[]));
@@ -138,6 +238,22 @@ static int NamespaceWhichCmd _ANSI_ARGS_((
static int SetNsNameFromAny _ANSI_ARGS_((
Tcl_Interp *interp, Tcl_Obj *objPtr));
static void UpdateStringOfNsName _ANSI_ARGS_((Tcl_Obj *objPtr));
+static int NsEnsembleImplementationCmd _ANSI_ARGS_((
+ ClientData clientData, Tcl_Interp *interp,
+ int objc, Tcl_Obj *CONST objv[]));
+static void BuildEnsembleConfig _ANSI_ARGS_((
+ EnsembleConfig *ensemblePtr));
+static int NsEnsembleStringOrder _ANSI_ARGS_((CONST VOID *strPtr1,
+ CONST VOID *strPtr2));
+static void DeleteEnsembleConfig _ANSI_ARGS_((
+ ClientData clientData));
+static void MakeCachedEnsembleCommand _ANSI_ARGS_((
+ Tcl_Obj *objPtr, EnsembleConfig *ensemblePtr,
+ CONST char *subcmdName, Tcl_Obj *prefixObjPtr));
+static void FreeEnsembleCmdRep _ANSI_ARGS_((Tcl_Obj *objPtr));
+static void DupEnsembleCmdRep _ANSI_ARGS_((Tcl_Obj *objPtr,
+ Tcl_Obj *copyPtr));
+static void StringOfEnsembleCmdRep _ANSI_ARGS_((Tcl_Obj *objPtr));
/*
* This structure defines a Tcl object type that contains a
@@ -153,6 +269,21 @@ Tcl_ObjType tclNsNameType = {
UpdateStringOfNsName, /* updateStringProc */
SetNsNameFromAny /* setFromAnyProc */
};
+
+/*
+ * This structure defines a Tcl object type that contains a reference
+ * to an ensemble subcommand (e.g. the "length" in [string length ab])
+ * It is used to cache the mapping between the subcommand itself and
+ * the real command that implements it.
+ */
+
+Tcl_ObjType tclEnsembleCmdType = {
+ "ensembleCommand", /* the type's name */
+ FreeEnsembleCmdRep, /* freeIntRepProc */
+ DupEnsembleCmdRep, /* dupIntRepProc */
+ StringOfEnsembleCmdRep, /* updateStringProc */
+ NULL /* setFromAnyProc */
+};
/*
*----------------------------------------------------------------------
@@ -534,6 +665,8 @@ Tcl_CreateNamespace(interp, name, clientData, deleteProc)
nsPtr->cmdResProc = NULL;
nsPtr->varResProc = NULL;
nsPtr->compiledVarResProc = NULL;
+ nsPtr->exportLookupEpoch = 0;
+ nsPtr->ensembles = NULL;
if (parentPtr != NULL) {
entryPtr = Tcl_CreateHashEntry(&parentPtr->childTable, simpleName,
@@ -604,6 +737,25 @@ Tcl_DeleteNamespace(namespacePtr)
Tcl_HashEntry *entryPtr;
/*
+ * If the namespace has associated ensemble commands, delete them
+ * first. This leaves the actual contents of the namespace alone
+ * (unless they are linked ensemble commands, of course.) Note
+ * that this code is actually reentrant so command delete traces
+ * won't purturb things badly.
+ */
+
+ while (nsPtr->ensembles != NULL) {
+ /*
+ * Splice out and link to indicate that we've already been
+ * killed.
+ */
+ EnsembleConfig *ensemblePtr = (EnsembleConfig *) nsPtr->ensembles;
+ nsPtr->ensembles = (Tcl_Ensemble *) ensemblePtr->next;
+ ensemblePtr->next = ensemblePtr;
+ Tcl_DeleteCommandFromToken(nsPtr->interp, ensemblePtr->token);
+ }
+
+ /*
* If the namespace is on the call frame stack, it is marked as "dying"
* (NS_DYING is OR'd into its flags): the namespace can't be looked up
* by name but its commands and variables are still usable by those
@@ -939,6 +1091,7 @@ Tcl_Export(interp, namespacePtr, pattern, resetListFirst)
}
ckfree((char *) nsPtr->exportArrayPtr);
nsPtr->exportArrayPtr = NULL;
+ TclInvalidateNsCmdLookup(nsPtr);
nsPtr->numExportPatterns = 0;
nsPtr->maxExportPatterns = 0;
}
@@ -1008,6 +1161,16 @@ Tcl_Export(interp, namespacePtr, pattern, resetListFirst)
nsPtr->exportArrayPtr[nsPtr->numExportPatterns] = patternCpy;
nsPtr->numExportPatterns++;
+
+ /*
+ * The list of commands actually exported from the namespace might
+ * have changed (probably will have!) However, we do not need to
+ * recompute this just yet; next time we need the info will be
+ * soon enough.
+ */
+
+ TclInvalidateNsCmdLookup(nsPtr);
+
return TCL_OK;
#undef INIT_EXPORT_PATTERNS
}
@@ -2484,13 +2647,13 @@ Tcl_NamespaceObjCmd(clientData, interp, objc, objv)
register Tcl_Obj *CONST objv[]; /* Argument objects. */
{
static CONST char *subCmds[] = {
- "children", "code", "current", "delete",
+ "children", "code", "current", "delete", "ensemble",
"eval", "exists", "export", "forget", "import",
"inscope", "origin", "parent", "qualifiers",
"tail", "which", (char *) NULL
};
enum NSSubCmdIdx {
- NSChildrenIdx, NSCodeIdx, NSCurrentIdx, NSDeleteIdx,
+ NSChildrenIdx, NSCodeIdx, NSCurrentIdx, NSDeleteIdx, NSEnsembleIdx,
NSEvalIdx, NSExistsIdx, NSExportIdx, NSForgetIdx, NSImportIdx,
NSInscopeIdx, NSOriginIdx, NSParentIdx, NSQualifiersIdx,
NSTailIdx, NSWhichIdx
@@ -2525,6 +2688,9 @@ Tcl_NamespaceObjCmd(clientData, interp, objc, objv)
case NSDeleteIdx:
result = NamespaceDeleteCmd(clientData, interp, objc, objv);
break;
+ case NSEnsembleIdx:
+ result = NamespaceEnsembleCmd(clientData, interp, objc, objv);
+ break;
case NSEvalIdx:
result = NamespaceEvalCmd(clientData, interp, objc, objv);
break;
@@ -3979,3 +4145,1427 @@ UpdateStringOfNsName(objPtr)
}
objPtr->length = length;
}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * NamespaceEnsembleCmd --
+ *
+ * Invoked to implement the "namespace ensemble" command that
+ * creates and manipulates ensembles built on top of namespaces.
+ * Handles the following syntax:
+ *
+ * namespace ensemble name ?dictionary?
+ *
+ * Results:
+ * Returns TCL_OK if successful, and TCL_ERROR if anything goes wrong.
+ *
+ * Side effects:
+ * Creates the ensemble for the namespace if one did not
+ * previously exist. Alternatively, alters the way that the
+ * ensemble's subcommand => implementation prefix is configured.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+NamespaceEnsembleCmd(dummy, interp, objc, objv)
+ ClientData dummy;
+ Tcl_Interp *interp;
+ int objc;
+ Tcl_Obj *CONST objv[];
+{
+ Namespace *nsPtr;
+ EnsembleConfig *ensemblePtr;
+ static CONST char *subcommands[] = {
+ "configure", "create", "exists", NULL
+ };
+ enum EnsSubcmds {
+ ENS_CONFIG, ENS_CREATE, ENS_EXISTS
+ };
+ static CONST char *createOptions[] = {
+ "-command", "-map", "-prefixes", "-subcommands", "-unknown", NULL
+ };
+ enum EnsCreateOpts {
+ CRT_CMD, CRT_MAP, CRT_PREFIX, CRT_SUBCMDS, CRT_UNKNOWN
+ };
+ static CONST char *configOptions[] = {
+ "-map", "-namespace", "-prefixes", "-subcommands", "-unknown", NULL
+ };
+ enum EnsConfigOpts {
+ CONF_MAP, CONF_NAMESPACE, CONF_PREFIX, CONF_SUBCMDS, CONF_UNKNOWN
+ };
+ int index;
+
+ nsPtr = (Namespace *) Tcl_GetCurrentNamespace(interp);
+ if (nsPtr == NULL || nsPtr->flags & NS_DEAD) {
+ if (!Tcl_InterpDeleted(interp)) {
+ Tcl_AppendResult(interp,
+ "tried to manipulate ensemble of deleted namespace", NULL);
+ }
+ return TCL_ERROR;
+ }
+
+ if (objc < 3) {
+ Tcl_WrongNumArgs(interp, 2, objv, "subcommand ?arg ...?");
+ return TCL_ERROR;
+ }
+ if (Tcl_GetIndexFromObj(interp, objv[2], subcommands, "subcommand", 0,
+ &index) != TCL_OK) {
+ return TCL_ERROR;
+ }
+
+ switch ((enum EnsSubcmds) index) {
+ case ENS_CREATE: {
+ char *name;
+ Tcl_DictSearch search;
+ Tcl_Obj *listObj, *nameObj = NULL;
+ int done, len, allocatedMapFlag = 0;
+ /*
+ * Defaults
+ */
+ Tcl_Obj *subcmdObj = NULL;
+ Tcl_Obj *mapObj = NULL;
+ int permitPrefix = 1;
+ Tcl_Obj *unknownObj = NULL;
+
+ objv += 3;
+ objc -= 3;
+
+ /*
+ * Work out what name to use for the command to create. If
+ * supplied, it is either fully specified or relative to the
+ * current namespace. If not supplied, it is exactly the name
+ * of the current namespace.
+ */
+
+ name = nsPtr->fullName;
+
+ /*
+ * Parse the option list, applying type checks as we go. Note
+ * that we are not incrementing any reference counts in the
+ * objects at this stage, so the presence of an option
+ * multiple times won't cause any memory leaks.
+ */
+
+ for (; objc>1 ; objc-=2,objv+=2 ) {
+ if (Tcl_GetIndexFromObj(interp, objv[0], createOptions, "option",
+ 0, &index) != TCL_OK) {
+ if (allocatedMapFlag) {
+ Tcl_DecrRefCount(mapObj);
+ }
+ return TCL_ERROR;
+ }
+ switch ((enum EnsCreateOpts) index) {
+ case CRT_CMD:
+ name = TclGetString(objv[1]);
+ continue;
+ case CRT_SUBCMDS:
+ if (Tcl_ListObjLength(interp, objv[1], &len) != TCL_OK) {
+ if (allocatedMapFlag) {
+ Tcl_DecrRefCount(mapObj);
+ }
+ return TCL_ERROR;
+ }
+ subcmdObj = (len > 0 ? objv[1] : NULL);
+ continue;
+ case CRT_MAP: {
+ Tcl_Obj *patchedDict = NULL, *subcmdObj;
+ /*
+ * Verify that the map is sensible.
+ */
+ if (Tcl_DictObjFirst(interp, objv[1], &search,
+ &subcmdObj, &listObj, &done) != TCL_OK) {
+ if (allocatedMapFlag) {
+ Tcl_DecrRefCount(mapObj);
+ }
+ return TCL_ERROR;
+ }
+ if (done) {
+ mapObj = NULL;
+ continue;
+ }
+ do {
+ Tcl_Obj **listv;
+ char *cmd;
+
+ if (Tcl_ListObjGetElements(interp, listObj, &len,
+ &listv) != TCL_OK) {
+ Tcl_DictObjDone(&search);
+ if (patchedDict) {
+ Tcl_DecrRefCount(patchedDict);
+ }
+ if (allocatedMapFlag) {
+ Tcl_DecrRefCount(mapObj);
+ }
+ return TCL_ERROR;
+ }
+ if (len < 1) {
+ Tcl_SetResult(interp,
+ "ensemble subcommand implementations "
+ "must be non-empty lists", TCL_STATIC);
+ Tcl_DictObjDone(&search);
+ if (patchedDict) {
+ Tcl_DecrRefCount(patchedDict);
+ }
+ if (allocatedMapFlag) {
+ Tcl_DecrRefCount(mapObj);
+ }
+ return TCL_ERROR;
+ }
+ cmd = TclGetString(listv[0]);
+ if (!(cmd[0] == ':' && cmd[1] == ':')) {
+ Tcl_Obj *newList = Tcl_NewListObj(len, listv);
+ Tcl_Obj *newCmd =
+ Tcl_NewStringObj(nsPtr->fullName, -1);
+ if (nsPtr->parentPtr) {
+ Tcl_AppendStringsToObj(newCmd, "::", NULL);
+ }
+ Tcl_AppendObjToObj(newCmd, listv[0]);
+ Tcl_ListObjReplace(NULL, newList, 0, 1, 1, &newCmd);
+ if (patchedDict == NULL) {
+ patchedDict = Tcl_DuplicateObj(objv[1]);
+ }
+ Tcl_DictObjPut(NULL, patchedDict, subcmdObj, newList);
+ }
+ Tcl_DictObjNext(&search, &subcmdObj, &listObj, &done);
+ } while (!done);
+ if (allocatedMapFlag) {
+ Tcl_DecrRefCount(mapObj);
+ }
+ mapObj = (patchedDict ? patchedDict : objv[1]);
+ if (patchedDict) {
+ allocatedMapFlag = 1;
+ }
+ continue;
+ }
+ case CRT_PREFIX:
+ if (Tcl_GetBooleanFromObj(interp, objv[1],
+ &permitPrefix) != TCL_OK) {
+ if (allocatedMapFlag) {
+ Tcl_DecrRefCount(mapObj);
+ }
+ return TCL_ERROR;
+ }
+ continue;
+ case CRT_UNKNOWN:
+ if (Tcl_ListObjLength(interp, objv[1], &len) != TCL_OK) {
+ if (allocatedMapFlag) {
+ Tcl_DecrRefCount(mapObj);
+ }
+ return TCL_ERROR;
+ }
+ unknownObj = (len > 0 ? objv[1] : NULL);
+ continue;
+ }
+ }
+
+ /*
+ * Make the name of the ensemble into a fully qualified name.
+ * This might allocate an object.
+ */
+
+ if (!(name[0] == ':' && name[1] == ':')) {
+ nameObj = Tcl_NewStringObj(nsPtr->fullName, -1);
+ if (nsPtr->parentPtr == NULL) {
+ Tcl_AppendStringsToObj(nameObj, name, NULL);
+ } else {
+ Tcl_AppendStringsToObj(nameObj, "::", name, NULL);
+ }
+ Tcl_IncrRefCount(nameObj);
+ name = TclGetString(nameObj);
+ }
+
+ /*
+ * Create the ensemble. Note that this might delete another
+ * ensemble linked to the same namespace, so we must be
+ * careful. However, we should be OK because we only link the
+ * namespace into the list once we've created it (and after
+ * any deletions have occurred.)
+ */
+
+ ensemblePtr = (EnsembleConfig *) ckalloc(sizeof(EnsembleConfig));
+ ensemblePtr->nsPtr = nsPtr;
+ ensemblePtr->epoch = 0;
+ Tcl_InitHashTable(&ensemblePtr->subcommandTable, TCL_STRING_KEYS);
+ ensemblePtr->subcommandArrayPtr = NULL;
+ ensemblePtr->subcmdList = subcmdObj;
+ if (subcmdObj != NULL) {
+ Tcl_IncrRefCount(subcmdObj);
+ }
+ ensemblePtr->subcommandDict = mapObj;
+ if (mapObj != NULL) {
+ Tcl_IncrRefCount(mapObj);
+ }
+ ensemblePtr->flags = (permitPrefix ? ENS_PREFIX : 0);
+ ensemblePtr->unknownHandler = unknownObj;
+ if (unknownObj != NULL) {
+ Tcl_IncrRefCount(unknownObj);
+ }
+ ensemblePtr->token = Tcl_CreateObjCommand(interp, name,
+ NsEnsembleImplementationCmd, (ClientData)ensemblePtr,
+ DeleteEnsembleConfig);
+ ensemblePtr->next = (EnsembleConfig *) nsPtr->ensembles;
+ nsPtr->ensembles = (Tcl_Ensemble *) ensemblePtr;
+ /*
+ * Trigger an eventual recomputation of the ensemble command
+ * set. Note that this is slightly tricky, as it means that
+ * we are not actually counting the number of namespace export
+ * actions, but it is the simplest way to go!
+ */
+ nsPtr->exportLookupEpoch++;
+ Tcl_SetResult(interp, name, TCL_VOLATILE);
+ if (nameObj != NULL) {
+ Tcl_DecrRefCount(nameObj);
+ }
+ return TCL_OK;
+ }
+
+ case ENS_EXISTS: {
+ Command *cmdPtr;
+ int flag;
+
+ if (objc != 4) {
+ Tcl_WrongNumArgs(interp, 3, objv, "cmdname");
+ return TCL_ERROR;
+ }
+ cmdPtr = (Command *)
+ Tcl_FindCommand(interp, TclGetString(objv[3]), 0, 0);
+ flag = (cmdPtr != NULL &&
+ cmdPtr->objProc == NsEnsembleImplementationCmd);
+ Tcl_SetBooleanObj(Tcl_GetObjResult(interp), flag);
+ return TCL_OK;
+ }
+
+ case ENS_CONFIG: {
+ char *cmdName;
+ Command *cmdPtr;
+
+ if (objc < 4 || (objc != 5 && objc & 1)) {
+ Tcl_WrongNumArgs(interp, 3, objv, "cmdname ?opt? ?value? ...");
+ return TCL_ERROR;
+ }
+ cmdName = TclGetString(objv[3]);
+ cmdPtr = (Command *)
+ Tcl_FindCommand(interp, cmdName, 0, TCL_LEAVE_ERR_MSG);
+ if (cmdPtr == NULL) {
+ return TCL_ERROR;
+ }
+ if (cmdPtr->objProc != NsEnsembleImplementationCmd) {
+ Tcl_AppendResult(interp, cmdName, " is not an ensemble command",
+ NULL);
+ return TCL_ERROR;
+ }
+ ensemblePtr = (EnsembleConfig *) cmdPtr->objClientData;
+
+ if (objc == 5) {
+ if (Tcl_GetIndexFromObj(interp, objv[4], configOptions, "option",
+ 0, &index) != TCL_OK) {
+ return TCL_ERROR;
+ }
+ switch ((enum EnsConfigOpts) index) {
+ case CONF_SUBCMDS:
+ if (ensemblePtr->subcmdList != NULL) {
+ Tcl_SetObjResult(interp, ensemblePtr->subcmdList);
+ }
+ break;
+ case CONF_MAP:
+ if (ensemblePtr->subcommandDict != NULL) {
+ Tcl_SetObjResult(interp, ensemblePtr->subcommandDict);
+ }
+ break;
+ case CONF_NAMESPACE:
+ Tcl_SetResult(interp, ensemblePtr->nsPtr->fullName,
+ TCL_VOLATILE);
+ break;
+ case CONF_PREFIX:
+ Tcl_SetObjResult(interp,
+ Tcl_NewBooleanObj(ensemblePtr->flags & ENS_PREFIX));
+ break;
+ case CONF_UNKNOWN:
+ if (ensemblePtr->unknownHandler != NULL) {
+ Tcl_SetObjResult(interp, ensemblePtr->unknownHandler);
+ }
+ break;
+ }
+ return TCL_OK;
+
+ } else if (objc == 4) {
+ /*
+ * Produce list of all information.
+ */
+
+ Tcl_Obj *resultObj;
+
+ TclNewObj(resultObj);
+ Tcl_ListObjAppendElement(NULL, resultObj,
+ Tcl_NewStringObj(configOptions[CONF_MAP], -1));
+ if (ensemblePtr->subcommandDict != NULL) {
+ Tcl_ListObjAppendElement(NULL, resultObj,
+ ensemblePtr->subcommandDict);
+ } else {
+ Tcl_ListObjAppendElement(NULL, resultObj, Tcl_NewObj());
+ }
+ Tcl_ListObjAppendElement(NULL, resultObj,
+ Tcl_NewStringObj(configOptions[CONF_NAMESPACE], -1));
+ Tcl_ListObjAppendElement(NULL, resultObj,
+ Tcl_NewStringObj(ensemblePtr->nsPtr->fullName, -1));
+ Tcl_ListObjAppendElement(NULL, resultObj,
+ Tcl_NewStringObj(configOptions[CONF_PREFIX], -1));
+ Tcl_ListObjAppendElement(NULL, resultObj,
+ Tcl_NewBooleanObj(ensemblePtr->flags & ENS_PREFIX));
+ Tcl_ListObjAppendElement(NULL, resultObj,
+ Tcl_NewStringObj(configOptions[CONF_SUBCMDS], -1));
+ if (ensemblePtr->subcmdList != NULL) {
+ Tcl_ListObjAppendElement(NULL, resultObj,
+ ensemblePtr->subcmdList);
+ } else {
+ Tcl_ListObjAppendElement(NULL, resultObj, Tcl_NewObj());
+ }
+ Tcl_ListObjAppendElement(NULL, resultObj,
+ Tcl_NewStringObj(configOptions[CONF_UNKNOWN], -1));
+ if (ensemblePtr->unknownHandler != NULL) {
+ Tcl_ListObjAppendElement(NULL, resultObj,
+ ensemblePtr->unknownHandler);
+ } else {
+ Tcl_ListObjAppendElement(NULL, resultObj, Tcl_NewObj());
+ }
+ Tcl_SetObjResult(interp, resultObj);
+ return TCL_OK;
+
+ } else {
+ Tcl_DictSearch search;
+ Tcl_Obj *listObj;
+ int done, len, allocatedMapFlag = 0;
+ /*
+ * Defaults
+ */
+ Tcl_Obj *subcmdObj = ensemblePtr->subcmdList;
+ Tcl_Obj *mapObj = ensemblePtr->subcommandDict;
+ Tcl_Obj *unknownObj = ensemblePtr->unknownHandler;
+ int permitPrefix = ensemblePtr->flags & ENS_PREFIX;
+
+ objv += 4;
+ objc -= 4;
+
+ /*
+ * Parse the option list, applying type checks as we go.
+ * Note that we are not incrementing any reference counts
+ * in the objects at this stage, so the presence of an
+ * option multiple times won't cause any memory leaks.
+ */
+
+ for (; objc>0 ; objc-=2,objv+=2 ) {
+ if (Tcl_GetIndexFromObj(interp, objv[0], configOptions,
+ "option", 0, &index) != TCL_OK) {
+ if (allocatedMapFlag) {
+ Tcl_DecrRefCount(mapObj);
+ }
+ return TCL_ERROR;
+ }
+ switch ((enum EnsConfigOpts) index) {
+ case CONF_SUBCMDS:
+ if (Tcl_ListObjLength(interp, objv[1], &len) != TCL_OK) {
+ if (allocatedMapFlag) {
+ Tcl_DecrRefCount(mapObj);
+ }
+ return TCL_ERROR;
+ }
+ subcmdObj = (len > 0 ? objv[1] : NULL);
+ continue;
+ case CONF_MAP: {
+ Tcl_Obj *patchedDict = NULL, *subcmdObj;
+ /*
+ * Verify that the map is sensible.
+ */
+ if (Tcl_DictObjFirst(interp, objv[1], &search,
+ &subcmdObj, &listObj, &done) != TCL_OK) {
+ if (allocatedMapFlag) {
+ Tcl_DecrRefCount(mapObj);
+ }
+ return TCL_ERROR;
+ }
+ if (done) {
+ mapObj = NULL;
+ continue;
+ }
+ do {
+ Tcl_Obj **listv;
+ char *cmd;
+
+ if (Tcl_ListObjGetElements(interp, listObj, &len,
+ &listv) != TCL_OK) {
+ Tcl_DictObjDone(&search);
+ if (patchedDict) {
+ Tcl_DecrRefCount(patchedDict);
+ }
+ if (allocatedMapFlag) {
+ Tcl_DecrRefCount(mapObj);
+ }
+ return TCL_ERROR;
+ }
+ if (len < 1) {
+ Tcl_SetResult(interp,
+ "ensemble subcommand implementations "
+ "must be non-empty lists", TCL_STATIC);
+ Tcl_DictObjDone(&search);
+ if (patchedDict) {
+ Tcl_DecrRefCount(patchedDict);
+ }
+ if (allocatedMapFlag) {
+ Tcl_DecrRefCount(mapObj);
+ }
+ return TCL_ERROR;
+ }
+ cmd = TclGetString(listv[0]);
+ if (!(cmd[0] == ':' && cmd[1] == ':')) {
+ Tcl_Obj *newList = Tcl_NewListObj(len, listv);
+ Tcl_Obj *newCmd =
+ Tcl_NewStringObj(nsPtr->fullName, -1);
+ if (nsPtr->parentPtr) {
+ Tcl_AppendStringsToObj(newCmd, "::", NULL);
+ }
+ Tcl_AppendObjToObj(newCmd, listv[0]);
+ Tcl_ListObjReplace(NULL, newList, 0,1, 1,&newCmd);
+ if (patchedDict == NULL) {
+ patchedDict = Tcl_DuplicateObj(objv[1]);
+ }
+ Tcl_DictObjPut(NULL, patchedDict, subcmdObj,
+ newList);
+ }
+ Tcl_DictObjNext(&search, &subcmdObj, &listObj, &done);
+ } while (!done);
+ if (allocatedMapFlag) {
+ Tcl_DecrRefCount(mapObj);
+ }
+ mapObj = (patchedDict ? patchedDict : objv[1]);
+ if (patchedDict) {
+ allocatedMapFlag = 1;
+ }
+ continue;
+ }
+ case CONF_NAMESPACE:
+ if (allocatedMapFlag) {
+ Tcl_DecrRefCount(mapObj);
+ }
+ Tcl_AppendResult(interp, "option -namespace is read-only",
+ NULL);
+ return TCL_ERROR;
+ case CONF_PREFIX:
+ if (Tcl_GetBooleanFromObj(interp, objv[1],
+ &permitPrefix) != TCL_OK) {
+ if (allocatedMapFlag) {
+ Tcl_DecrRefCount(mapObj);
+ }
+ return TCL_ERROR;
+ }
+ continue;
+ case CONF_UNKNOWN:
+ if (Tcl_ListObjLength(interp, objv[1], &len) != TCL_OK) {
+ if (allocatedMapFlag) {
+ Tcl_DecrRefCount(mapObj);
+ }
+ return TCL_ERROR;
+ }
+ unknownObj = (len > 0 ? objv[1] : NULL);
+ continue;
+ }
+ }
+
+ /*
+ * Update the namespace now that we've finished the
+ * parsing stage.
+ */
+
+ if (ensemblePtr->subcmdList != subcmdObj) {
+ if (ensemblePtr->subcmdList != NULL) {
+ Tcl_DecrRefCount(ensemblePtr->subcmdList);
+ }
+ ensemblePtr->subcmdList = subcmdObj;
+ if (subcmdObj != NULL) {
+ Tcl_IncrRefCount(subcmdObj);
+ }
+ }
+ if (ensemblePtr->subcommandDict != mapObj) {
+ if (ensemblePtr->subcommandDict != NULL) {
+ Tcl_DecrRefCount(ensemblePtr->subcommandDict);
+ }
+ ensemblePtr->subcommandDict = mapObj;
+ if (mapObj != NULL) {
+ Tcl_IncrRefCount(mapObj);
+ }
+ }
+ if (ensemblePtr->unknownHandler != unknownObj) {
+ if (ensemblePtr->unknownHandler != NULL) {
+ Tcl_DecrRefCount(ensemblePtr->unknownHandler);
+ }
+ ensemblePtr->unknownHandler = unknownObj;
+ if (unknownObj != NULL) {
+ Tcl_IncrRefCount(unknownObj);
+ }
+ }
+ if (permitPrefix) {
+ ensemblePtr->flags |= ENS_PREFIX;
+ } else {
+ ensemblePtr->flags &= ~ENS_PREFIX;
+ }
+ /*
+ * Trigger an eventual recomputation of the ensemble
+ * command set. Note that this is slightly tricky, as it
+ * means that we are not actually counting the number of
+ * namespace export actions, but it is the simplest way to
+ * go! Also note that this nsPtr and ensemblePtr->nsPtr
+ * are quite possibly not the same namespace; we want to
+ * bump the epoch for the ensemble's namespace, not the
+ * current namespace.
+ */
+ ensemblePtr->nsPtr->exportLookupEpoch++;
+ return TCL_OK;
+ }
+ }
+
+ default:
+ panic("unexpected ensemble command");
+ }
+ return TCL_OK;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * NsEnsembleImplementationCmd --
+ *
+ * Implements an ensemble of commands (being those exported by a
+ * namespace other than the global namespace) as a command with
+ * the same (short) name as the namespace in the parent namespace.
+ *
+ * Results:
+ * A standard Tcl result code. Will be TCL_ERROR if the command
+ * is not an unambiguous prefix of any command exported by the
+ * ensemble's namespace.
+ *
+ * Side effects:
+ * Depends on the command within the namespace that gets executed.
+ * If the ensemble itself returns TCL_ERROR, a descriptive error
+ * message will be placed in the interpreter's result.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+NsEnsembleImplementationCmd(clientData, interp, objc, objv)
+ ClientData clientData;
+ Tcl_Interp *interp;
+ int objc;
+ Tcl_Obj *CONST objv[];
+{
+ EnsembleConfig *ensemblePtr = (EnsembleConfig *) clientData;
+ /* The ensemble itself. */
+ Tcl_Obj **tempObjv; /* Space used to construct the list of
+ * arguments to pass to the command
+ * that implements the ensemble
+ * subcommand. */
+ int result; /* The result of the subcommand
+ * execution. */
+ Tcl_Obj *prefixObj; /* An object containing the prefix
+ * words of the command that implements
+ * the subcommand. */
+ Tcl_HashEntry *hPtr; /* Used for efficient lookup of fully
+ * specified but not yet cached command
+ * names. */
+ Tcl_Obj **prefixObjv; /* The list of objects to substitute in
+ * as the target command prefix. */
+ int prefixObjc; /* Size of prefixObjv of course! */
+ int reparseCount = 0; /* Number of reparses. */
+
+ if (objc < 2) {
+ Tcl_WrongNumArgs(interp, 1, objv, "subcommand ?argument ...?");
+ return TCL_ERROR;
+ }
+
+ restartEnsembleParse:
+ if (ensemblePtr->nsPtr->flags & NS_DEAD) {
+ /*
+ * Don't know how we got here, but make things give up quickly.
+ */
+ if (!Tcl_InterpDeleted(interp)) {
+ Tcl_AppendResult(interp,
+ "ensemble activated for deleted namespace", NULL);
+ }
+ return TCL_ERROR;
+ }
+
+ if (ensemblePtr->epoch != ensemblePtr->nsPtr->exportLookupEpoch) {
+ ensemblePtr->epoch = ensemblePtr->nsPtr->exportLookupEpoch;
+ BuildEnsembleConfig(ensemblePtr);
+ } else {
+ /*
+ * Table of subcommands is still valid; therefore there might
+ * be a valid cache of discovered information which we can
+ * reuse. Do the check here, and if we're still valid, we can
+ * jump straight to the part where we do the invocation of the
+ * subcommand.
+ */
+
+ if (objv[1]->typePtr == &tclEnsembleCmdType) {
+ EnsembleCmdRep *ensembleCmd = (EnsembleCmdRep *)
+ objv[1]->internalRep.otherValuePtr;
+ if (ensembleCmd->nsPtr == ensemblePtr->nsPtr &&
+ ensembleCmd->epoch == ensemblePtr->epoch) {
+ prefixObj = ensembleCmd->realPrefixObj;
+ goto runSubcommand;
+ }
+ }
+ }
+
+ /*
+ * Look in the hashtable for the subcommand name; this is the
+ * fastest way of all.
+ */
+
+ hPtr = Tcl_FindHashEntry(&ensemblePtr->subcommandTable,
+ TclGetString(objv[1]));
+ if (hPtr != NULL) {
+ char *fullName = Tcl_GetHashKey(&ensemblePtr->subcommandTable, hPtr);
+ prefixObj = (Tcl_Obj *) Tcl_GetHashValue(hPtr);
+
+ /*
+ * Cache for later in the subcommand object.
+ */
+
+ MakeCachedEnsembleCommand(objv[1], ensemblePtr, fullName, prefixObj);
+ } else if (!(ensemblePtr->flags & ENS_PREFIX)) {
+ /*
+ * Can't find and we are prohibited from using unambiguous prefixes.
+ */
+ goto unknownOrAmbiguousSubcommand;
+ } else {
+ /*
+ * If we've not already confirmed the command with the hash as
+ * part of building our export table, we need to scan the
+ * sorted array for matches.
+ */
+
+ char *subcmdName; /* Name of the subcommand, or unique
+ * prefix of it (will be an error for
+ * a non-unique prefix). */
+ char *fullName = NULL; /* Full name of the subcommand. */
+ int stringLength, i;
+ int tableLength = ensemblePtr->subcommandTable.numEntries;
+
+ subcmdName = TclGetString(objv[1]);
+ stringLength = objv[1]->length;
+ for (i=0 ; i<tableLength ; i++) {
+ register int cmp = strncmp(subcmdName,
+ ensemblePtr->subcommandArrayPtr[i],
+ (unsigned)stringLength);
+ if (cmp == 0) {
+ if (fullName != NULL) {
+ /*
+ * Since there's never the exact-match case to
+ * worry about (hash search filters this), getting
+ * here indicates that our subcommand is an
+ * ambiguous prefix of (at least) two exported
+ * subcommands, which is an error case.
+ */
+ goto unknownOrAmbiguousSubcommand;
+ }
+ fullName = ensemblePtr->subcommandArrayPtr[i];
+ } else if (cmp == 1) {
+ /*
+ * Because we are searching a sorted table, we can now
+ * stop searching because we have gone past anything
+ * that could possibly match.
+ */
+ break;
+ }
+ }
+ if (fullName == NULL) {
+ /*
+ * The subcommand is not a prefix of anything, so bail out!
+ */
+ goto unknownOrAmbiguousSubcommand;
+ }
+ hPtr = Tcl_FindHashEntry(&ensemblePtr->subcommandTable, fullName);
+ if (hPtr == NULL) {
+ panic("full name %s not found in supposedly synchronized hash",
+ fullName);
+ }
+ prefixObj = (Tcl_Obj *) Tcl_GetHashValue(hPtr);
+
+ /*
+ * Cache for later in the subcommand object.
+ */
+
+ MakeCachedEnsembleCommand(objv[1], ensemblePtr, fullName, prefixObj);
+ }
+
+ runSubcommand:
+ /*
+ * Do the real work of execution of the subcommand by building an
+ * array of objects (note that this is potentially not the same
+ * length as the number of arguments to this ensemble command),
+ * populating it and then feeding it back through the main
+ * command-lookup engine. In theory, we could look up the command
+ * in the namespace ourselves, as we already have the namespace in
+ * which it is guaranteed to exist, but we don't do that (the
+ * cacheing of the command object used should help with that.)
+ */
+
+ Tcl_IncrRefCount(prefixObj);
+ runResultingSubcommand:
+ Tcl_ListObjGetElements(NULL, prefixObj, &prefixObjc, &prefixObjv);
+ tempObjv = (Tcl_Obj **) ckalloc(sizeof(Tcl_Obj *)*(objc-2+prefixObjc));
+ memcpy(tempObjv, prefixObjv, sizeof(Tcl_Obj *) * prefixObjc);
+ memcpy(tempObjv+prefixObjc, objv+2, sizeof(Tcl_Obj *) * (objc-2));
+ result = Tcl_EvalObjv(interp, objc-2+prefixObjc, tempObjv, 0);
+ Tcl_DecrRefCount(prefixObj);
+ ckfree((char *)tempObjv);
+ return result;
+
+ unknownOrAmbiguousSubcommand:
+ /*
+ * Have not been able to match the subcommand asked for with a
+ * real subcommand that we export. See whether a handler has been
+ * registered for dealing with this situation. Will only call (at
+ * most) once for any particular ensemble invocation.
+ */
+
+ if (ensemblePtr->unknownHandler != NULL && reparseCount++ < 1) {
+ int paramc, i;
+ Tcl_Obj **paramv, *unknownCmd;
+ char *ensName = TclGetString(objv[0]);
+
+ unknownCmd = Tcl_DuplicateObj(ensemblePtr->unknownHandler);
+ if (ensName[0] == ':') {
+ Tcl_ListObjAppendElement(NULL, unknownCmd, objv[0]);
+ } else {
+ Tcl_Obj *qualEnsembleObj =
+ Tcl_NewStringObj(Tcl_GetCurrentNamespace(interp)->fullName,-1);
+ if (Tcl_GetCurrentNamespace(interp)->parentPtr) {
+ Tcl_AppendStringsToObj(qualEnsembleObj, "::", ensName, NULL);
+ } else {
+ Tcl_AppendStringsToObj(qualEnsembleObj, ensName, NULL);
+ }
+ Tcl_ListObjAppendElement(NULL, unknownCmd, qualEnsembleObj);
+ }
+ for (i=1 ; i<objc ; i++) {
+ Tcl_ListObjAppendElement(NULL, unknownCmd, objv[i]);
+ }
+ Tcl_ListObjGetElements(NULL, unknownCmd, &paramc, &paramv);
+ Tcl_Preserve(ensemblePtr);
+ Tcl_IncrRefCount(unknownCmd);
+ result = Tcl_EvalObjv(interp, paramc, paramv, 0);
+ if (result == TCL_OK) {
+ prefixObj = Tcl_GetObjResult(interp);
+ Tcl_IncrRefCount(prefixObj);
+ Tcl_DecrRefCount(unknownCmd);
+ Tcl_Release(ensemblePtr);
+ Tcl_ResetResult(interp);
+ if (ensemblePtr->flags & ENS_DEAD) {
+ Tcl_DecrRefCount(prefixObj);
+ Tcl_SetResult(interp,
+ "unknown subcommand handler deleted its ensemble",
+ TCL_STATIC);
+ return TCL_ERROR;
+ }
+
+ /*
+ * Namespace is still there. Check if the result is a
+ * valid list. If it is, and it is non-empty, that list
+ * is what we are using as our replacement.
+ */
+
+ if (Tcl_ListObjLength(interp, prefixObj, &prefixObjc) != TCL_OK) {
+ Tcl_DecrRefCount(prefixObj);
+ Tcl_AddErrorInfo(interp,
+ "\n while parsing result of ensemble unknown subcommand handler");
+ return TCL_ERROR;
+ }
+ if (prefixObjc > 0) {
+ /*
+ * Not 'runSubcommand' because we want to get the
+ * object refcounting right.
+ */
+ goto runResultingSubcommand;
+ }
+
+ /*
+ * Namespace alive & empty result => reparse.
+ */
+
+ goto restartEnsembleParse;
+ }
+ if (!Tcl_InterpDeleted(interp)) {
+ if (result != TCL_ERROR) {
+ Tcl_ResetResult(interp);
+ Tcl_SetResult(interp,
+ "unknown subcommand handler returned bad code: ",
+ TCL_STATIC);
+ switch (result) {
+ case TCL_RETURN:
+ Tcl_AppendResult(interp, "return", NULL);
+ break;
+ case TCL_BREAK:
+ Tcl_AppendResult(interp, "break", NULL);
+ break;
+ case TCL_CONTINUE:
+ Tcl_AppendResult(interp, "continue", NULL);
+ break;
+ default: {
+ char buf[TCL_INTEGER_SPACE];
+ sprintf(buf, "%d", result);
+ Tcl_AppendResult(interp, buf, NULL);
+ }
+ }
+ Tcl_AddErrorInfo(interp,
+ "\n result of ensemble unknown subcommand handler: ");
+ Tcl_AddErrorInfo(interp, TclGetString(unknownCmd));
+ } else {
+ Tcl_AddErrorInfo(interp,
+ "\n (ensemble unknown subcommand handler)");
+ }
+ }
+ Tcl_DecrRefCount(unknownCmd);
+ Tcl_Release(ensemblePtr);
+ return TCL_ERROR;
+ }
+ /*
+ * Cannot determine what subcommand to hand off to, so generate a
+ * (standard) failure message. Note the one odd case compared
+ * with standard ensemble-like command, which is where a namespace
+ * has no exported commands at all...
+ */
+ Tcl_ResetResult(interp);
+ if (ensemblePtr->subcommandTable.numEntries == 0) {
+ Tcl_AppendStringsToObj(Tcl_GetObjResult(interp),
+ "unknown subcommand \"", TclGetString(objv[1]),
+ "\": namespace ", ensemblePtr->nsPtr->fullName,
+ " does not export any commands", NULL);
+ return TCL_ERROR;
+ }
+ Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), "unknown ",
+ (ensemblePtr->flags & ENS_PREFIX ? "or ambiguous " : ""),
+ "subcommand \"", TclGetString(objv[1]), "\": must be ", NULL);
+ if (ensemblePtr->subcommandTable.numEntries == 1) {
+ Tcl_AppendStringsToObj(Tcl_GetObjResult(interp),
+ ensemblePtr->subcommandArrayPtr[0], NULL);
+ } else {
+ int i;
+ for (i=0 ; i<ensemblePtr->subcommandTable.numEntries-1 ; i++) {
+ Tcl_AppendStringsToObj(Tcl_GetObjResult(interp),
+ ensemblePtr->subcommandArrayPtr[i], ", ", NULL);
+ }
+ Tcl_AppendStringsToObj(Tcl_GetObjResult(interp),
+ "or ", ensemblePtr->subcommandArrayPtr[i], NULL);
+ }
+ return TCL_ERROR;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * MakeCachedEnsembleCommand --
+ *
+ * Cache what we've computed so far; it's not nice to repeatedly
+ * copy strings about. Note that to do this, we start by
+ * deleting any old representation that there was (though if it
+ * was an out of date ensemble rep, we can skip some of the
+ * deallocation process.)
+ *
+ * Results:
+ * None
+ *
+ * Side effects:
+ * Alters the internal representation of the first object parameter.
+ *
+ *----------------------------------------------------------------------
+ */
+static void
+MakeCachedEnsembleCommand(objPtr, ensemblePtr, subcommandName, prefixObjPtr)
+ Tcl_Obj *objPtr;
+ EnsembleConfig *ensemblePtr;
+ CONST char *subcommandName;
+ Tcl_Obj *prefixObjPtr;
+{
+ register EnsembleCmdRep *ensembleCmd;
+ int length;
+
+ if (objPtr->typePtr == &tclEnsembleCmdType) {
+ ensembleCmd = (EnsembleCmdRep *) objPtr->internalRep.otherValuePtr;
+ Tcl_DecrRefCount(ensembleCmd->realPrefixObj);
+ ensembleCmd->nsPtr->refCount--;
+ if ((ensembleCmd->nsPtr->refCount == 0)
+ && (ensembleCmd->nsPtr->flags & NS_DEAD)) {
+ NamespaceFree(ensembleCmd->nsPtr);
+ }
+ ckfree(ensembleCmd->fullSubcmdName);
+ } else {
+ /*
+ * Kill the old internal rep, and replace it with a brand new
+ * one of our own.
+ */
+ if ((objPtr->typePtr != NULL)
+ && (objPtr->typePtr->freeIntRepProc != NULL)) {
+ objPtr->typePtr->freeIntRepProc(objPtr);
+ }
+ ensembleCmd = (EnsembleCmdRep *) ckalloc(sizeof(EnsembleCmdRep));
+ objPtr->internalRep.otherValuePtr = (VOID *) ensembleCmd;
+ objPtr->typePtr = &tclEnsembleCmdType;
+ }
+
+ /*
+ * Populate the internal rep.
+ */
+ ensembleCmd->nsPtr = ensemblePtr->nsPtr;
+ ensemblePtr->nsPtr->refCount++;
+ ensembleCmd->realPrefixObj = prefixObjPtr;
+ length = strlen(subcommandName)+1;
+ ensembleCmd->fullSubcmdName = ckalloc((unsigned) length);
+ memcpy(ensembleCmd->fullSubcmdName, subcommandName, (unsigned) length);
+ Tcl_IncrRefCount(ensembleCmd->realPrefixObj);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * DeleteEnsembleConfig --
+ *
+ * Destroys the data structure used to represent an ensemble.
+ * This is called when the ensemble's command is deleted (which
+ * happens automatically if the ensemble's namespace is deleted.)
+ * Maintainers should note that ensembles should be deleted by
+ * deleting their commands.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Memory is (eventually) deallocated.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+DeleteEnsembleConfig(clientData)
+ ClientData clientData;
+{
+ EnsembleConfig *ensemblePtr = (EnsembleConfig *)clientData;
+ Namespace *nsPtr = ensemblePtr->nsPtr;
+ Tcl_HashSearch search;
+ Tcl_HashEntry *hEnt;
+
+ /*
+ * Unlink from the ensemble chain if it has not been marked as
+ * having been done already.
+ */
+
+ if (ensemblePtr->next != ensemblePtr) {
+ EnsembleConfig *ensPtr = (EnsembleConfig *) nsPtr->ensembles;
+ if (ensPtr == ensemblePtr) {
+ nsPtr->ensembles = (Tcl_Ensemble *) ensemblePtr->next;
+ } else {
+ while (ensPtr != NULL) {
+ if (ensPtr->next == ensemblePtr) {
+ ensPtr->next = ensemblePtr->next;
+ break;
+ }
+ ensPtr = ensPtr->next;
+ }
+ }
+ }
+
+ /*
+ * Mark the namespace as dead so code that uses Tcl_Preserve() can
+ * tell whether disaster happened anyway.
+ */
+
+ ensemblePtr->flags |= ENS_DEAD;
+
+ /*
+ * Kill the pointer-containing fields.
+ */
+
+ if (ensemblePtr->subcommandTable.numEntries != 0) {
+ ckfree((char *)ensemblePtr->subcommandArrayPtr);
+ }
+ hEnt = Tcl_FirstHashEntry(&ensemblePtr->subcommandTable, &search);
+ while (hEnt != NULL) {
+ Tcl_Obj *prefixObj = (Tcl_Obj *) Tcl_GetHashValue(hEnt);
+ Tcl_DecrRefCount(prefixObj);
+ hEnt = Tcl_NextHashEntry(&search);
+ }
+ Tcl_DeleteHashTable(&ensemblePtr->subcommandTable);
+ if (ensemblePtr->subcmdList != NULL) {
+ Tcl_DecrRefCount(ensemblePtr->subcmdList);
+ }
+ if (ensemblePtr->subcommandDict != NULL) {
+ Tcl_DecrRefCount(ensemblePtr->subcommandDict);
+ }
+ if (ensemblePtr->unknownHandler != NULL) {
+ Tcl_DecrRefCount(ensemblePtr->unknownHandler);
+ }
+
+ /*
+ * Arrange for the structure to be reclaimed. Note that this is
+ * complex because we have to make sure that we can react sensibly
+ * when an ensemble is deleted during the process of initialising
+ * the ensemble (especially the unknown callback.)
+ */
+
+ Tcl_EventuallyFree((ClientData) ensemblePtr, TCL_DYNAMIC);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * BuildEnsembleConfig --
+ *
+ * Create the internal data structures that describe how an
+ * ensemble looks, being a hash mapping from the full command
+ * name to the Tcl list that describes the implementation prefix
+ * words, and a sorted array of all the full command names to
+ * allow for reasonably efficient unambiguous prefix handling.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Reallocates and rebuilds the hash table and array stored at
+ * the ensemblePtr argument. For large ensembles or large
+ * namespaces, this is a potentially expensive operation.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+BuildEnsembleConfig(ensemblePtr)
+ EnsembleConfig *ensemblePtr;
+{
+ Tcl_HashSearch search; /* Used for scanning the set of
+ * commands in the namespace that
+ * backs up this ensemble. */
+ int i, j, isNew;
+ Tcl_HashTable *hash = &ensemblePtr->subcommandTable;
+ Tcl_HashEntry *hPtr;
+
+ if (hash->numEntries != 0) {
+ /*
+ * Remove pre-existing table.
+ */
+ ckfree((char *)ensemblePtr->subcommandArrayPtr);
+ Tcl_DeleteHashTable(hash);
+ Tcl_InitHashTable(hash, TCL_STRING_KEYS);
+ }
+
+ /*
+ * See if we've got an export list. If so, we will only export
+ * exactly those commands, which may be either implemented by the
+ * prefix in the subcommandDict or mapped directly onto the
+ * namespace's commands.
+ */
+
+ if (ensemblePtr->subcmdList != NULL) {
+ Tcl_Obj **subcmdv, *target, *cmdObj, *cmdPrefixObj;
+ int subcmdc;
+
+ Tcl_ListObjGetElements(NULL, ensemblePtr->subcmdList, &subcmdc,
+ &subcmdv);
+ for (i=0 ; i<subcmdc ; i++) {
+ char *name = TclGetString(subcmdv[i]);
+
+ hPtr = Tcl_CreateHashEntry(hash, name, &isNew);
+
+ /* Skip non-unique cases. */
+ if (!isNew) {
+ continue;
+ }
+ /*
+ * Look in our dictionary (if present) for the command.
+ */
+ if (ensemblePtr->subcommandDict != NULL) {
+ Tcl_DictObjGet(NULL, ensemblePtr->subcommandDict, subcmdv[i],
+ &target);
+ if (target != NULL) {
+ Tcl_SetHashValue(hPtr, (ClientData) target);
+ Tcl_IncrRefCount(target);
+ continue;
+ }
+ }
+ /*
+ * Not there, so map onto the namespace. Note in this
+ * case that we do not guarantee that the command is
+ * actually there; that is the programmer's responsibility
+ * (or [::unknown] of course).
+ */
+ cmdObj = Tcl_NewStringObj(ensemblePtr->nsPtr->fullName, -1);
+ if (ensemblePtr->nsPtr->parentPtr != NULL) {
+ Tcl_AppendStringsToObj(cmdObj, "::", name, NULL);
+ } else {
+ Tcl_AppendStringsToObj(cmdObj, name, NULL);
+ }
+ cmdPrefixObj = Tcl_NewListObj(1, &cmdObj);
+ Tcl_SetHashValue(hPtr, (ClientData) cmdPrefixObj);
+ Tcl_IncrRefCount(cmdPrefixObj);
+ }
+ } else if (ensemblePtr->subcommandDict != NULL) {
+ /*
+ * No subcmd list, but we do have a mapping dictionary so we
+ * should use the keys of that. Convert the dictionary's
+ * contents into the form required for the ensemble's internal
+ * hashtable.
+ */
+ Tcl_DictSearch dictSearch;
+ Tcl_Obj *keyObj, *valueObj;
+ int done;
+
+ Tcl_DictObjFirst(NULL, ensemblePtr->subcommandDict, &dictSearch,
+ &keyObj, &valueObj, &done);
+ while (!done) {
+ char *name = TclGetString(keyObj);
+ hPtr = Tcl_CreateHashEntry(hash, name, &isNew);
+ Tcl_SetHashValue(hPtr, (ClientData) valueObj);
+ Tcl_IncrRefCount(valueObj);
+ Tcl_DictObjNext(&dictSearch, &keyObj, &valueObj, &done);
+ }
+ } else {
+ /*
+ * Discover what commands are actually exported by the
+ * namespace. What we have is an array of patterns and a hash
+ * table whose keys are the command names exported by the
+ * namespace (the contents do not matter here.) We must find
+ * out what commands are actually exported by filtering each
+ * command in the namespace against each of the patterns in
+ * the export list. Note that we use an intermediate hash
+ * table to make memory management easier, and because that
+ * makes exact matching far easier too.
+ *
+ * Suggestion for future enhancement: compute the unique
+ * prefixes and place them in the hash too, which should make
+ * for even faster matching.
+ */
+
+ hPtr = Tcl_FirstHashEntry(&ensemblePtr->nsPtr->cmdTable, &search);
+ for (; hPtr!= NULL ; hPtr=Tcl_NextHashEntry(&search)) {
+ char *nsCmdName = /* Name of command in namespace. */
+ Tcl_GetHashKey(&ensemblePtr->nsPtr->cmdTable, hPtr);
+
+ for (i=0 ; i<ensemblePtr->nsPtr->numExportPatterns ; i++) {
+ if (Tcl_StringMatch(nsCmdName,
+ ensemblePtr->nsPtr->exportArrayPtr[i])) {
+ hPtr = Tcl_CreateHashEntry(hash, nsCmdName, &isNew);
+
+ /*
+ * Remember, hash entries have a full reference to
+ * the substituted part of the command (as a list)
+ * as their content!
+ */
+
+ if (isNew) {
+ Tcl_Obj *cmdObj, *cmdPrefixObj;
+
+ TclNewObj(cmdObj);
+ Tcl_AppendStringsToObj(cmdObj,
+ ensemblePtr->nsPtr->fullName,
+ (ensemblePtr->nsPtr->parentPtr ? "::" : ""),
+ nsCmdName, NULL);
+ cmdPrefixObj = Tcl_NewListObj(1, &cmdObj);
+ Tcl_SetHashValue(hPtr, (ClientData) cmdPrefixObj);
+ Tcl_IncrRefCount(cmdPrefixObj);
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ if (hash->numEntries == 0) {
+ ensemblePtr->subcommandArrayPtr = NULL;
+ return;
+ }
+
+ /*
+ * Create a sorted array of all subcommands in the ensemble; hash
+ * tables are all very well for a quick look for an exact match,
+ * but they can't determine things like whether a string is a
+ * prefix of another (not without lots of preparation anyway) and
+ * they're no good for when we're generating the error message
+ * either.
+ *
+ * We do this by filling an array with the names (we use the hash
+ * keys directly to save a copy, since any time we change the
+ * array we change the hash too, and vice versa) and running
+ * quicksort over the array.
+ */
+
+ ensemblePtr->subcommandArrayPtr = (char **)
+ ckalloc(sizeof(char *) * hash->numEntries);
+
+ /*
+ * Fill array from both ends as this makes us less likely to end
+ * up with performance problems in qsort(), which is good. Note
+ * that doing this makes this code much more opaque, but the naive
+ * alternatve:
+ *
+ * for (hPtr=Tcl_FirstHashEntry(hash,&search),i=0 ;
+ * hPtr!=NULL ; hPtr=Tcl_NextHashEntry(&search),i++) {
+ * ensemblePtr->subcommandArrayPtr[i] =
+ * Tcl_GetHashKey(hash, &hPtr);
+ * }
+ *
+ * can produce long runs of precisely ordered table entries when
+ * the commands in the namespace are declared in a sorted fashion
+ * (an ordering some people like) and the hashing functions (or
+ * the command names themselves) are fairly unfortunate. By
+ * filling from both ends, it requires active malice (and probably
+ * a debugger) to get qsort() to have awful runtime behaviour.
+ */
+
+ i = 0;
+ j = hash->numEntries;
+ hPtr = Tcl_FirstHashEntry(hash, &search);
+ while (hPtr != NULL) {
+ ensemblePtr->subcommandArrayPtr[i++] = Tcl_GetHashKey(hash, hPtr);
+ hPtr = Tcl_NextHashEntry(&search);
+ if (hPtr == NULL) {
+ break;
+ }
+ ensemblePtr->subcommandArrayPtr[--j] = Tcl_GetHashKey(hash, hPtr);
+ hPtr = Tcl_NextHashEntry(&search);
+ }
+ if (hash->numEntries > 1) {
+ qsort(ensemblePtr->subcommandArrayPtr, (unsigned)hash->numEntries,
+ sizeof(char *), NsEnsembleStringOrder);
+ }
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * NsEnsembleStringOrder --
+ *
+ * Helper function to compare two pointers to two strings for use
+ * with qsort().
+ *
+ * Results:
+ * -1 if the first string is smaller, 1 if the second string is
+ * smaller, and 0 if they are equal.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+NsEnsembleStringOrder(strPtr1, strPtr2)
+ CONST VOID *strPtr1, *strPtr2;
+{
+ return strcmp(*(CONST char **)strPtr1, *(CONST char **)strPtr2);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * FreeEnsembleCmdRep --
+ *
+ * Destroys the internal representation of a Tcl_Obj that has been
+ * holding information about a command in an ensemble.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Memory is deallocated. If this held the last reference to a
+ * namespace's main structure, that main structure will also be
+ * destroyed.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+FreeEnsembleCmdRep(objPtr)
+ Tcl_Obj *objPtr;
+{
+ EnsembleCmdRep *ensembleCmd = (EnsembleCmdRep *)
+ objPtr->internalRep.otherValuePtr;
+
+ Tcl_DecrRefCount(ensembleCmd->realPrefixObj);
+ ckfree(ensembleCmd->fullSubcmdName);
+ ensembleCmd->nsPtr->refCount--;
+ if ((ensembleCmd->nsPtr->refCount == 0)
+ && (ensembleCmd->nsPtr->flags & NS_DEAD)) {
+ NamespaceFree(ensembleCmd->nsPtr);
+ }
+ ckfree((char *)ensembleCmd);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * DupEnsembleCmdRep --
+ *
+ * Makes one Tcl_Obj into a copy of another that is a subcommand
+ * of an ensemble.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Memory is allocated, and the namespace that the ensemble is
+ * built on top of gains another reference.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+DupEnsembleCmdRep(objPtr, copyPtr)
+ Tcl_Obj *objPtr, *copyPtr;
+{
+ EnsembleCmdRep *ensembleCmd = (EnsembleCmdRep *)
+ objPtr->internalRep.otherValuePtr;
+ EnsembleCmdRep *ensembleCopy = (EnsembleCmdRep *)
+ ckalloc(sizeof(EnsembleCmdRep));
+ int length = strlen(ensembleCmd->fullSubcmdName);
+
+ copyPtr->typePtr = &tclEnsembleCmdType;
+ copyPtr->internalRep.otherValuePtr = (VOID *) ensembleCopy;
+ ensembleCopy->nsPtr = ensembleCmd->nsPtr;
+ ensembleCopy->epoch = ensembleCmd->epoch;
+ ensembleCopy->nsPtr->refCount++;
+ ensembleCopy->realPrefixObj = ensembleCmd->realPrefixObj;
+ Tcl_IncrRefCount(ensembleCopy->realPrefixObj);
+ ensembleCopy->fullSubcmdName = ckalloc((unsigned) length+1);
+ memcpy(ensembleCopy->fullSubcmdName, ensembleCmd->fullSubcmdName,
+ (unsigned) length+1);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * StringOfEnsembleCmdRep --
+ *
+ * Creates a string representation of a Tcl_Obj that holds a
+ * subcommand of an ensemble.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * The object gains a string (UTF-8) representation.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+StringOfEnsembleCmdRep(objPtr)
+ Tcl_Obj *objPtr;
+{
+ EnsembleCmdRep *ensembleCmd = (EnsembleCmdRep *)
+ objPtr->internalRep.otherValuePtr;
+ int length = strlen(ensembleCmd->fullSubcmdName);
+
+ objPtr->length = length;
+ objPtr->bytes = ckalloc((unsigned) length+1);
+ memcpy(objPtr->bytes, ensembleCmd->fullSubcmdName, (unsigned) length+1);
+}