Tcl Source Code

Artifact [6d040042f9]
Login

Artifact 6d040042f940ea77fd7e4b46d27b6131ef8bc754:

Attachment "ensemble.patch" to ticket [650046ffff] added by dkf 2003-02-14 21:39:39.
Index: generic/tclBasic.c
===================================================================
RCS file: /cvsroot/tcl/tcl/generic/tclBasic.c,v
retrieving revision 1.72
diff -u -r1.72 tclBasic.c
--- generic/tclBasic.c	3 Feb 2003 20:16:52 -0000	1.72
+++ generic/tclBasic.c	14 Feb 2003 14:22:05 -0000
@@ -1167,7 +1169,7 @@
     if (strstr(hiddenCmdToken, "::") != NULL) {
         Tcl_AppendStringsToObj(Tcl_GetObjResult(interp),
                 "cannot use namespace qualifiers as hidden command",
-		"token (rename)", (char *) NULL);
+		" token (rename)", (char *) NULL);
         return TCL_ERROR;
     }
 
@@ -1241,6 +1243,14 @@
     }
 
     /*
+     * The list of command exported from the namespace might have
+     * changed.  However, we do not need to recompute this just yet;
+     * next time we need the info will be soon enough.
+     */
+
+    TclInvalidateNsCmdLookup(cmdPtr->nsPtr);
+
+    /*
      * Now link the hash table entry with the command structure.
      * We ensured above that the nsPtr was right.
      */
@@ -1370,6 +1380,14 @@
     }
 
     /*
+     * The list of command exported from the namespace might have
+     * changed.  However, we do not need to recompute this just yet;
+     * next time we need the info will be soon enough.
+     */
+
+    TclInvalidateNsCmdLookup(nsPtr);
+
+    /*
      * Remove the hash entry for the command from the interpreter hidden
      * command table.
      */
@@ -1508,6 +1526,14 @@
 
 	     ckfree((char*) Tcl_GetHashValue(hPtr));
 	}
+    } else {
+	/*
+	 * The list of command exported from the namespace might have
+	 * changed.  However, we do not need to recompute this just
+	 * yet; next time we need the info will be soon enough.
+	 */
+
+	TclInvalidateNsCmdLookup(nsPtr);
     }
     cmdPtr = (Command *) ckalloc(sizeof(Command));
     Tcl_SetHashValue(hPtr, cmdPtr);
@@ -1670,6 +1696,14 @@
 
 	     ckfree((char *) Tcl_GetHashValue(hPtr));
 	}
+    } else {
+	/*
+	 * The list of command exported from the namespace might have
+	 * changed.  However, we do not need to recompute this just
+	 * yet; next time we need the info will be soon enough.
+	 */
+
+	TclInvalidateNsCmdLookup(nsPtr);
     }
     cmdPtr = (Command *) ckalloc(sizeof(Command));
     Tcl_SetHashValue(hPtr, cmdPtr);
@@ -2006,6 +2040,16 @@
     }
 
     /*
+     * The list of command exported from the namespace might have
+     * changed.  However, we do not need to recompute this just yet;
+     * next time we need the info will be soon enough.  These might
+     * refer to the same variable, but that's no big deal.
+     */
+
+    TclInvalidateNsCmdLookup(cmdNsPtr);
+    TclInvalidateNsCmdLookup(cmdPtr->nsPtr);
+
+    /*
      * Script for rename traces can delete the command "oldName".
      * Therefore increment the reference count for cmdPtr so that
      * it's Command structure is freed only towards the end of this
@@ -2448,7 +2492,15 @@
 	}
 	cmdPtr->tracePtr = NULL;
     }
-    
+
+    /*
+     * The list of command exported from the namespace might have
+     * changed.  However, we do not need to recompute this just yet;
+     * next time we need the info will be soon enough.
+     */
+
+    TclInvalidateNsCmdLookup(cmdPtr->nsPtr);
+
     /*
      * If the command being deleted has a compile procedure, increment the
      * interpreter's compileEpoch to invalidate its compiled code. This
Index: generic/tclInt.h
===================================================================
RCS file: /cvsroot/tcl/tcl/generic/tclInt.h,v
retrieving revision 1.118
diff -u -r1.118 tclInt.h
--- generic/tclInt.h	10 Feb 2003 10:26:25 -0000	1.118
+++ generic/tclInt.h	14 Feb 2003 14:22:07 -0000
@@ -212,6 +212,16 @@
 				  * within LookupCompiledLocal to resolve
 				  * variable references within the namespace
 				  * at compile time. */
+    int exportLookupEpoch;	/* Incremented whenever a command is added to
+				 * a namespace, removed from a namespace or
+				 * the exports of a namespace are changed.
+				 * Allows TIP#112-driven command lists to be
+				 * validated efficiently. */
+    Tcl_Command ensembleToken;	/* The token for the command that provides
+				 * ensemble support for this namespace, or
+				 * NULL if the command has been deleted (or
+				 * never existed; the global namespace never
+				 * has an ensemble command.) */
 } Namespace;
 
 /*
@@ -1597,6 +1607,7 @@
 #ifndef TCL_WIDE_INT_IS_LONG
 extern Tcl_ObjType	tclWideIntType;
 #endif
+extern Tcl_ObjType	tclEnsembleCmdType;
 
 /*
  * Variables denoting the hash key types defined in the core.
@@ -2269,11 +2285,27 @@
  *         CONST Tcl_UniChar *ct, unsigned long n));
  *----------------------------------------------------------------
  */
+
 #ifdef WORDS_BIGENDIAN
 #   define TclUniCharNcmp(cs,ct,n) memcmp((cs),(ct),(n)*sizeof(Tcl_UniChar))
 #else /* !WORDS_BIGENDIAN */
 #   define TclUniCharNcmp Tcl_UniCharNcmp
 #endif /* WORDS_BIGENDIAN */
+
+/*
+ *----------------------------------------------------------------
+ * Macro used by the Tcl core to increment a namespace's export
+ * export epoch counter.
+ * The ANSI C "prototype" for this macro is:
+ *
+ * EXTERN void TclInvalidateNsCmdLookup _ANSI_ARGS_((Namespace *nsPtr));
+ *----------------------------------------------------------------
+ */
+
+#define TclInvalidateNsCmdLookup(nsPtr) \
+    if ((nsPtr)->numExportPatterns) { \
+	(nsPtr)->exportLookupEpoch++; \
+    }
 
 #include "tclIntDecls.h"
 
Index: generic/tclNamesp.c
===================================================================
RCS file: /cvsroot/tcl/tcl/generic/tclNamesp.c,v
retrieving revision 1.31
diff -u -r1.31 tclNamesp.c
--- generic/tclNamesp.c	15 Jul 2002 22:18:07 -0000	1.31
+++ generic/tclNamesp.c	14 Feb 2003 14:22:08 -0000
@@ -10,6 +10,7 @@
  * 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
@@ -74,15 +75,55 @@
 } 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 EnsembleData {
+    Namespace *nsPtr;		/* The namspace backing this ensemble up. */
+    int epoch;			/* The epoch at which this ensemble's table of
+				 * exported commands is valid. */
+    int numSubcommands;		/* Number of commands exported by the
+				 * namespace as ensemble subcommands. */
+    char **subcommandArrayPtr;	/* Array of ensemble subcommand names. */
+    Tcl_HashTable subcommandTable;
+				/* Hash table of ensemble subcommand names,
+				 * which are its keys so this also provides
+				 * the storage management for those subcommand
+				 * names. */
+} EnsembleData;
+
+/*
+ * 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 EnsembleCmd {
+    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 *realCmdObj;	/* Object containing the fully qualified
+				 * command in the namespace that implements
+				 * this ensemble subcommand. */
+} EnsembleCmd;
+
+/*
  * 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));
@@ -138,6 +179,17 @@
 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 int		NsEnsembleStringOrder _ANSI_ARGS_((CONST VOID *strPtr1,
+			    CONST VOID *strPtr2));
+static void		DeleteEnsembleData _ANSI_ARGS_((
+			    ClientData clientData));
+static void		FreeEnsembleCmdRep _ANSI_ARGS_((Tcl_Obj *objPtr));
+static void		DupEnsembleCmdRep _ANSI_ARGS_((Tcl_Obj *objPtr,
+			    Tcl_Obj *copyPtr));
+static void		StringOfEnsembleCmd _ANSI_ARGS_((Tcl_Obj *objPtr));
 
 /*
  * This structure defines a Tcl object type that contains a
@@ -153,6 +205,21 @@
     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 */
+    StringOfEnsembleCmd,	/* updateStringProc */
+    NULL			/* setFromAnyProc */
+};
 
 /*
  *----------------------------------------------------------------------
@@ -534,11 +601,25 @@
     nsPtr->cmdResProc        = NULL;
     nsPtr->varResProc        = NULL;
     nsPtr->compiledVarResProc = NULL;
+    nsPtr->exportLookupEpoch = 0;
+    nsPtr->ensembleToken     = NULL;
 
     if (parentPtr != NULL) {
+	EnsembleData *ensemblePtr;
+
         entryPtr = Tcl_CreateHashEntry(&parentPtr->childTable, simpleName,
 	        &newEntry);
         Tcl_SetHashValue(entryPtr, (ClientData) nsPtr);
+
+	ensemblePtr = (EnsembleData *) ckalloc(sizeof(EnsembleData));
+	ensemblePtr->nsPtr = nsPtr;
+	Tcl_InitHashTable(&ensemblePtr->subcommandTable, TCL_STRING_KEYS);
+	ensemblePtr->subcommandArrayPtr = NULL;
+	ensemblePtr->numSubcommands = 0;
+	ensemblePtr->epoch = 0;
+	nsPtr->ensembleToken = Tcl_CreateObjCommand(interp, simpleName,
+		NsEnsembleImplementationCmd, (ClientData)ensemblePtr,
+		DeleteEnsembleData);
     }
 
     /*
@@ -604,6 +685,20 @@
     Tcl_HashEntry *entryPtr;
 
     /*
+     * If the namespace has an associated ensemble command, delete it
+     * first.  This leaves the actual contents of the namespace alone.
+     * Note that a namespace might not have an ensemble command
+     * associated with it; the global namespace never does, this might
+     * be a reentrant call to Tcl_DeleteNamespace(), or the user might
+     * have nuked the command.
+     */
+
+    if (nsPtr->ensembleToken != NULL) {
+	Tcl_DeleteCommandFromToken(nsPtr->interp, nsPtr->ensembleToken);
+	nsPtr->ensembleToken = NULL;
+    }
+
+    /*
      * 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
@@ -941,6 +1036,7 @@
 	    }
 	    ckfree((char *) nsPtr->exportArrayPtr);
 	    nsPtr->exportArrayPtr = NULL;
+	    TclInvalidateNsCmdLookup(nsPtr);
 	    nsPtr->numExportPatterns = 0;
 	    nsPtr->maxExportPatterns = 0;
 	}
@@ -1010,6 +1106,16 @@
     
     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
 }
@@ -3980,4 +4086,512 @@
 	objPtr->bytes[length] = '\0';
     }
     objPtr->length = length;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * 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[];
+{
+    EnsembleData *ensemblePtr = (EnsembleData *) clientData;
+					/* The ensemble itself. */
+    EnsembleCmd *ensembleCmd;		/* Internal rep. of a subcommand in
+					 * the ensemble. */
+    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. */
+    char *fullCommandName;		/* Full name of the implementation
+					 * command within the context of the
+					 * namespace. */
+    int tableLength;			/* Number of subcommands in the
+					 * ensemble. */
+    Tcl_HashTable *hash = &ensemblePtr->subcommandTable;
+					/* Hash table of all subcommands in
+					 * the ensemble. Note that each hash
+					 * entry has no value associated with
+					 * it; it is only there for its key. */
+    Tcl_Obj *cmdObj;			/* An object containing the full name
+					 * of the implementation subcommand. */
+    Tcl_HashEntry *hPtr;
+    int stringLength;
+    int i, j;
+
+    if (objc < 2) {
+	Tcl_WrongNumArgs(interp, 1, objv, "subcommand ?argument ...?");
+	return TCL_ERROR;
+    }
+
+    if (ensemblePtr->epoch != ensemblePtr->nsPtr->exportLookupEpoch) {
+	Tcl_HashSearch search;		/* Used for scanning the set of
+					 * commands in the namespace that
+					 * backs up this ensemble. */
+
+	if (ensemblePtr->numSubcommands != 0) {
+	    /*
+	     * Remove pre-existing table.
+	     */
+	    ckfree((char *)ensemblePtr->subcommandArrayPtr);
+	    Tcl_DeleteHashTable(hash);
+	    Tcl_InitHashTable(hash, TCL_STRING_KEYS);
+	}
+	/*
+	 * 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.
+	 */
+	for (hPtr=Tcl_FirstHashEntry(&ensemblePtr->nsPtr->cmdTable, &search);
+	     hPtr!= NULL ; hPtr=Tcl_NextHashEntry(&search)) {
+	    int isNew;			/* Dummy. */
+	    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])) {
+		    Tcl_CreateHashEntry(hash, nsCmdName, &isNew);
+		    /*
+		     * Remember, hash entries have no content!
+		     */
+		    break;
+		}
+	    }
+	}
+	tableLength = ensemblePtr->numSubcommands = hash->numEntries;
+	if (tableLength == 0) {
+	    ensemblePtr->subcommandArrayPtr = NULL;
+	} else {
+	    /*
+	     * 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 *)*tableLength);
+	    /*
+	     * 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 = tableLength;
+	    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 (tableLength > 1) {
+		qsort(ensemblePtr->subcommandArrayPtr,
+			(unsigned)tableLength, sizeof(char *),
+			NsEnsembleStringOrder);
+	    }
+	}
+	ensemblePtr->epoch = ensemblePtr->nsPtr->exportLookupEpoch;
+    } 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) {
+	    ensembleCmd = (EnsembleCmd *) objv[1]->internalRep.otherValuePtr;
+	    if (ensembleCmd->nsPtr == ensemblePtr->nsPtr &&
+		ensembleCmd->epoch == ensemblePtr->epoch) {
+		cmdObj = ensembleCmd->realCmdObj;
+		goto runSubcommand;
+	    }
+	}
+
+	tableLength = ensemblePtr->numSubcommands;
+    }
+
+    /*
+     * Look in the hashtable for the subcommand name; this is the
+     * fastest way of all.
+     */
+
+    hPtr = Tcl_FindHashEntry(hash, TclGetString(objv[1]));
+    if (hPtr != NULL) {
+	fullCommandName = Tcl_GetHashKey(hash, hPtr);
+    } 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.
+	 */
+
+	int matched = 0;		/* Have we found anything yet? */
+	char *subcmdName = Tcl_GetStringFromObj(objv[1], &stringLength);
+	for (i=0 ; i<tableLength ; i++) {
+	    register int cmp = strncmp(subcmdName,
+		    ensemblePtr->subcommandArrayPtr[i],
+		    (unsigned)stringLength);
+	    if (cmp == 0) {
+		if (matched) {
+		    /*
+		     * 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;
+		}
+		matched = 1;
+		fullCommandName = 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 (!matched) {
+	    /*
+	     * The subcommand is not a prefix of anything, so bail out!
+	     */
+	    goto unknownOrAmbiguousSubcommand;
+	}
+    }
+
+    /*
+     * Now we are here, we know the name of the command to use as
+     * implementation and the name of the namespace that contains it
+     * (namespaces know their names!) so we can construct the
+     * qualified name of the real command that we're going to execute.
+     */
+    cmdObj = Tcl_NewStringObj(ensemblePtr->nsPtr->fullName, -1);
+    Tcl_AppendStringsToObj(cmdObj, "::", fullCommandName, NULL);
+
+    /*
+     * 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.)
+     */
+    if (objv[1]->typePtr == &tclEnsembleCmdType) {
+	ensembleCmd = (EnsembleCmd *) objv[1]->internalRep.otherValuePtr;
+	Tcl_DecrRefCount(ensembleCmd->realCmdObj);
+	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 ((objv[1]->typePtr != NULL)
+		&& (objv[1]->typePtr->freeIntRepProc != NULL)) {
+	    objv[1]->typePtr->freeIntRepProc(objv[1]);
+	}
+	ensembleCmd = (EnsembleCmd *) ckalloc(sizeof(EnsembleCmd));
+	objv[1]->internalRep.otherValuePtr = (VOID *) ensembleCmd;
+	objv[1]->typePtr = &tclEnsembleCmdType;
+    }
+    /*
+     * Populate the internal rep.
+     */
+    ensembleCmd->nsPtr = ensemblePtr->nsPtr;
+    ensembleCmd->realCmdObj = cmdObj;
+    stringLength = strlen(fullCommandName)+1;
+    ensembleCmd->fullSubcmdName = ckalloc((unsigned)stringLength);
+    memcpy(ensembleCmd->fullSubcmdName, fullCommandName,
+	    (unsigned)stringLength);
+    Tcl_IncrRefCount(ensembleCmd->realCmdObj);
+
+ runSubcommand:
+    /*
+     * Do the real work of execution of the subcommand by building an
+     * array of objects (note that this is one shorter than 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.)
+     */
+    tempObjv = (Tcl_Obj **) ckalloc(sizeof(Tcl_Obj *)*(objc-1));
+    tempObjv[0] = cmdObj;
+    Tcl_IncrRefCount(cmdObj);
+    memcpy(tempObjv+1, objv+2, sizeof(Tcl_Obj *)*(objc-2));
+    result = Tcl_EvalObjv(interp, objc-1, tempObjv, 0);
+    Tcl_DecrRefCount(cmdObj);
+    ckfree((char *)tempObjv);
+    return result;
+
+ unknownOrAmbiguousSubcommand:
+    /*
+     * 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->numSubcommands == 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 or ambiguous subcommand \"", TclGetString(objv[1]),
+	    "\": must be ", NULL);
+    if (ensemblePtr->numSubcommands == 1) {
+	Tcl_AppendStringsToObj(Tcl_GetObjResult(interp),
+		ensemblePtr->subcommandArrayPtr[0], NULL);
+    } else {
+	for (i=0 ; i<ensemblePtr->numSubcommands-1 ; i++) {
+	    Tcl_AppendStringsToObj(Tcl_GetObjResult(interp),
+		    ensemblePtr->subcommandArrayPtr[i], ", ", NULL);
+	}
+	Tcl_AppendStringsToObj(Tcl_GetObjResult(interp),
+		"or ", ensemblePtr->subcommandArrayPtr[i], NULL);
+    }
+    return TCL_ERROR;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * 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);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * DeleteEnsembleData --
+ *
+ *	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 the ensemble command's
+ *	implementation does not read from this structure once it has
+ *	invoked a subcommand, so no attempt is made to Tcl_Preserve()
+ *	this structure.  Also note that because an ensemble command is
+ *	always deleted very early in the destruction of a namespace,
+ *	there is no reference count in the main namespace structure
+ *	associated with the ensemble structure.
+ *
+ * Results:
+ *	None.
+ *
+ * Side effects:
+ *	Memory is deallocated.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+DeleteEnsembleData(clientData)
+    ClientData clientData;
+{
+    EnsembleData *ensemblePtr = (EnsembleData *)clientData;
+
+    if (ensemblePtr->numSubcommands != 0) {
+	ckfree((char *)ensemblePtr->subcommandArrayPtr);
+    }
+    Tcl_DeleteHashTable(&ensemblePtr->subcommandTable);
+    ckfree((char *)ensemblePtr);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * 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;
+{
+    EnsembleCmd *ensembleCmd = (EnsembleCmd *)
+	    objPtr->internalRep.otherValuePtr;
+
+    Tcl_DecrRefCount(ensembleCmd->realCmdObj);
+    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;
+{
+    EnsembleCmd *ensembleCmd = (EnsembleCmd *)
+	    objPtr->internalRep.otherValuePtr;
+    EnsembleCmd *ensembleCopy = (EnsembleCmd *) ckalloc(sizeof(EnsembleCmd));
+    unsigned 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->realCmdObj = ensembleCmd->realCmdObj;
+    Tcl_IncrRefCount(ensembleCopy->realCmdObj);
+    ensembleCopy->fullSubcmdName = ckalloc(length+1);
+    memcpy(ensembleCopy->fullSubcmdName, ensembleCmd->fullSubcmdName,
+	    length+1);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * StringOfEnsembleCmd --
+ *
+ *	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
+StringOfEnsembleCmd(objPtr)
+    Tcl_Obj *objPtr;
+{
+    EnsembleCmd *ensembleCmd = (EnsembleCmd *)
+	    objPtr->internalRep.otherValuePtr;
+    unsigned int length = strlen(ensembleCmd->fullSubcmdName);
+
+    objPtr->length = length;
+    objPtr->bytes = ckalloc(length+1);
+    memcpy(objPtr->bytes, ensembleCmd->fullSubcmdName, length+1);
 }
Index: generic/tclObj.c
===================================================================
RCS file: /cvsroot/tcl/tcl/generic/tclObj.c,v
retrieving revision 1.42
diff -u -r1.42 tclObj.c
--- generic/tclObj.c	17 Jan 2003 22:11:02 -0000	1.42
+++ generic/tclObj.c	14 Feb 2003 14:22:08 -0000
@@ -243,6 +243,7 @@
     Tcl_RegisterObjType(&tclArraySearchType);
     Tcl_RegisterObjType(&tclIndexType);
     Tcl_RegisterObjType(&tclNsNameType);
+    Tcl_RegisterObjType(&tclEnsembleCmdType);
     Tcl_RegisterObjType(&tclCmdNameType);
 
 #ifdef TCL_COMPILE_STATS