summaryrefslogtreecommitdiffstats
path: root/generic/tclDictObj.c
diff options
context:
space:
mode:
Diffstat (limited to 'generic/tclDictObj.c')
-rw-r--r--generic/tclDictObj.c3170
1 files changed, 3170 insertions, 0 deletions
diff --git a/generic/tclDictObj.c b/generic/tclDictObj.c
new file mode 100644
index 0000000..8f3ce3a
--- /dev/null
+++ b/generic/tclDictObj.c
@@ -0,0 +1,3170 @@
+/*
+ * tclDictObj.c --
+ *
+ * This file contains functions that implement the Tcl dict object type
+ * and its accessor command.
+ *
+ * Copyright (c) 2002 by Donal K. Fellows.
+ *
+ * See the file "license.terms" for information on usage and redistribution of
+ * this file, and for a DISCLAIMER OF ALL WARRANTIES.
+ */
+
+#include "tclInt.h"
+#include "tommath.h"
+
+/*
+ * Forward declaration.
+ */
+struct Dict;
+
+/*
+ * Prototypes for functions defined later in this file:
+ */
+
+static void DeleteDict(struct Dict *dict);
+static int DictAppendCmd(ClientData dummy, Tcl_Interp *interp,
+ int objc, Tcl_Obj *const *objv);
+static int DictCreateCmd(ClientData dummy, Tcl_Interp *interp,
+ int objc, Tcl_Obj *const *objv);
+static int DictExistsCmd(ClientData dummy, Tcl_Interp *interp,
+ int objc, Tcl_Obj *const *objv);
+static int DictFilterCmd(ClientData dummy, Tcl_Interp *interp,
+ int objc, Tcl_Obj *const *objv);
+static int DictForCmd(ClientData dummy, Tcl_Interp *interp,
+ int objc, Tcl_Obj *const *objv);
+static int DictGetCmd(ClientData dummy, Tcl_Interp *interp,
+ int objc, Tcl_Obj *const *objv);
+static int DictIncrCmd(ClientData dummy, Tcl_Interp *interp,
+ int objc, Tcl_Obj *const *objv);
+static int DictInfoCmd(ClientData dummy, Tcl_Interp *interp,
+ int objc, Tcl_Obj *const *objv);
+static int DictKeysCmd(ClientData dummy, Tcl_Interp *interp,
+ int objc, Tcl_Obj *const *objv);
+static int DictLappendCmd(ClientData dummy, Tcl_Interp *interp,
+ int objc, Tcl_Obj *const *objv);
+static int DictMergeCmd(ClientData dummy, Tcl_Interp *interp,
+ int objc, Tcl_Obj *const *objv);
+static int DictRemoveCmd(ClientData dummy, Tcl_Interp *interp,
+ int objc, Tcl_Obj *const *objv);
+static int DictReplaceCmd(ClientData dummy, Tcl_Interp *interp,
+ int objc, Tcl_Obj *const *objv);
+static int DictSetCmd(ClientData dummy, Tcl_Interp *interp,
+ int objc, Tcl_Obj *const *objv);
+static int DictSizeCmd(ClientData dummy, Tcl_Interp *interp,
+ int objc, Tcl_Obj *const *objv);
+static int DictUnsetCmd(ClientData dummy, Tcl_Interp *interp,
+ int objc, Tcl_Obj *const *objv);
+static int DictUpdateCmd(ClientData dummy, Tcl_Interp *interp,
+ int objc, Tcl_Obj *const *objv);
+static int DictValuesCmd(ClientData dummy, Tcl_Interp *interp,
+ int objc, Tcl_Obj *const *objv);
+static int DictWithCmd(ClientData dummy, Tcl_Interp *interp,
+ int objc, Tcl_Obj *const *objv);
+static void DupDictInternalRep(Tcl_Obj *srcPtr, Tcl_Obj *copyPtr);
+static void FreeDictInternalRep(Tcl_Obj *dictPtr);
+static void InvalidateDictChain(Tcl_Obj *dictObj);
+static int SetDictFromAny(Tcl_Interp *interp, Tcl_Obj *objPtr);
+static void UpdateStringOfDict(Tcl_Obj *dictPtr);
+static Tcl_HashEntry * AllocChainEntry(Tcl_HashTable *tablePtr,void *keyPtr);
+static inline void InitChainTable(struct Dict *dict);
+static inline void DeleteChainTable(struct Dict *dict);
+static inline Tcl_HashEntry *CreateChainEntry(struct Dict *dict,
+ Tcl_Obj *keyPtr, int *newPtr);
+static inline int DeleteChainEntry(struct Dict *dict, Tcl_Obj *keyPtr);
+
+/*
+ * Table of dict subcommand names and implementations.
+ */
+
+static const EnsembleImplMap implementationMap[] = {
+ {"append", DictAppendCmd, TclCompileDictAppendCmd },
+ {"create", DictCreateCmd, NULL },
+ {"exists", DictExistsCmd, NULL },
+ {"filter", DictFilterCmd, NULL },
+ {"for", DictForCmd, TclCompileDictForCmd },
+ {"get", DictGetCmd, TclCompileDictGetCmd },
+ {"incr", DictIncrCmd, TclCompileDictIncrCmd },
+ {"info", DictInfoCmd, NULL },
+ {"keys", DictKeysCmd, NULL },
+ {"lappend", DictLappendCmd, TclCompileDictLappendCmd },
+ {"merge", DictMergeCmd, NULL },
+ {"remove", DictRemoveCmd, NULL },
+ {"replace", DictReplaceCmd, NULL },
+ {"set", DictSetCmd, TclCompileDictSetCmd },
+ {"size", DictSizeCmd, NULL },
+ {"unset", DictUnsetCmd, NULL },
+ {"update", DictUpdateCmd, TclCompileDictUpdateCmd },
+ {"values", DictValuesCmd, NULL },
+ {"with", DictWithCmd, NULL },
+ {NULL, NULL, NULL}
+};
+
+/*
+ * Internal representation of the entries in the hash table that backs a
+ * dictionary.
+ */
+
+typedef struct ChainEntry {
+ Tcl_HashEntry entry;
+ struct ChainEntry *prevPtr;
+ struct ChainEntry *nextPtr;
+} ChainEntry;
+
+/*
+ * Internal representation of a dictionary.
+ *
+ * The internal representation of a dictionary object is a hash table (with
+ * Tcl_Objs for both keys and values), a reference count and epoch number for
+ * detecting concurrent modifications of the dictionary, and a pointer to the
+ * parent object (used when invalidating string reps of pathed dictionary
+ * trees) which is NULL in normal use. The fact that hash tables know (with
+ * appropriate initialisation) already about objects makes key management /so/
+ * much easier!
+ *
+ * Reference counts are used to enable safe iteration across hashes while
+ * allowing the type of the containing object to be modified.
+ */
+
+typedef struct Dict {
+ Tcl_HashTable table; /* Object hash table to store mapping in. */
+ ChainEntry *entryChainHead; /* Linked list of all entries in the
+ * dictionary. Used for doing traversal of the
+ * entries in the order that they are
+ * created. */
+ ChainEntry *entryChainTail; /* Other end of linked list of all entries in
+ * the dictionary. Used for doing traversal of
+ * the entries in the order that they are
+ * created. */
+ int epoch; /* Epoch counter */
+ int refcount; /* Reference counter (see above) */
+ Tcl_Obj *chain; /* Linked list used for invalidating the
+ * string representations of updated nested
+ * dictionaries. */
+} Dict;
+
+/*
+ * The structure below defines the dictionary object type by means of
+ * functions that can be invoked by generic object code.
+ */
+
+Tcl_ObjType tclDictType = {
+ "dict",
+ FreeDictInternalRep, /* freeIntRepProc */
+ DupDictInternalRep, /* dupIntRepProc */
+ UpdateStringOfDict, /* updateStringProc */
+ SetDictFromAny /* setFromAnyProc */
+};
+
+/*
+ * The type of the specially adapted version of the Tcl_Obj*-containing hash
+ * table defined in the tclObj.c code. This version differs in that it
+ * allocates a bit more space in each hash entry in order to hold the pointers
+ * used to keep the hash entries in a linked list.
+ *
+ * Note that this type of hash table is *only* suitable for direct use in
+ * *this* file. Everything else should use the dict iterator API.
+ */
+
+static Tcl_HashKeyType chainHashType = {
+ TCL_HASH_KEY_TYPE_VERSION,
+ 0,
+ TclHashObjKey,
+ TclCompareObjKeys,
+ AllocChainEntry,
+ TclFreeObjEntry
+};
+
+/***** START OF FUNCTIONS IMPLEMENTING DICT CORE API *****/
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * AllocChainEntry --
+ *
+ * Allocate space for a Tcl_HashEntry containing the Tcl_Obj * key, and
+ * which has a bit of extra space afterwards for storing pointers to the
+ * rest of the chain of entries (the extra pointers are left NULL).
+ *
+ * Results:
+ * The return value is a pointer to the created entry.
+ *
+ * Side effects:
+ * Increments the reference count on the object.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static Tcl_HashEntry *
+AllocChainEntry(
+ Tcl_HashTable *tablePtr,
+ void *keyPtr)
+{
+ Tcl_Obj *objPtr = keyPtr;
+ ChainEntry *cPtr;
+
+ cPtr = (ChainEntry *) ckalloc(sizeof(ChainEntry));
+ cPtr->entry.key.oneWordValue = (char *) objPtr;
+ Tcl_IncrRefCount(objPtr);
+ cPtr->entry.clientData = NULL;
+ cPtr->prevPtr = cPtr->nextPtr = NULL;
+
+ return &cPtr->entry;
+}
+
+/*
+ * Helper functions that disguise most of the details relating to how the
+ * linked list of hash entries is managed. In particular, these manage the
+ * creation of the table and initializing of the chain, the deletion of the
+ * table and chain, the adding of an entry to the chain, and the removal of an
+ * entry from the chain.
+ */
+
+static inline void
+InitChainTable(
+ Dict *dict)
+{
+ Tcl_InitCustomHashTable(&dict->table, TCL_CUSTOM_PTR_KEYS,
+ &chainHashType);
+ dict->entryChainHead = dict->entryChainTail = NULL;
+}
+
+static inline void
+DeleteChainTable(
+ Dict *dict)
+{
+ ChainEntry *cPtr;
+
+ for (cPtr=dict->entryChainHead ; cPtr!=NULL ; cPtr=cPtr->nextPtr) {
+ Tcl_Obj *valuePtr = Tcl_GetHashValue(&cPtr->entry);
+
+ TclDecrRefCount(valuePtr);
+ }
+ Tcl_DeleteHashTable(&dict->table);
+}
+
+static inline Tcl_HashEntry *
+CreateChainEntry(
+ Dict *dict,
+ Tcl_Obj *keyPtr,
+ int *newPtr)
+{
+ ChainEntry *cPtr = (ChainEntry *)
+ Tcl_CreateHashEntry(&dict->table, (char *) keyPtr, newPtr);
+
+ /*
+ * If this is a new entry in the hash table, stitch it into the chain.
+ */
+
+ if (*newPtr) {
+ cPtr->nextPtr = NULL;
+ if (dict->entryChainHead == NULL) {
+ cPtr->prevPtr = NULL;
+ dict->entryChainHead = cPtr;
+ dict->entryChainTail = cPtr;
+ } else {
+ cPtr->prevPtr = dict->entryChainTail;
+ dict->entryChainTail->nextPtr = cPtr;
+ dict->entryChainTail = cPtr;
+ }
+ }
+
+ return &cPtr->entry;
+}
+
+static inline int
+DeleteChainEntry(
+ Dict *dict,
+ Tcl_Obj *keyPtr)
+{
+ ChainEntry *cPtr = (ChainEntry *)
+ Tcl_FindHashEntry(&dict->table, (char *) keyPtr);
+
+ if (cPtr == NULL) {
+ return 0;
+ } else {
+ Tcl_Obj *valuePtr = Tcl_GetHashValue(&cPtr->entry);
+ TclDecrRefCount(valuePtr);
+ }
+
+ /*
+ * Unstitch from the chain.
+ */
+
+ if (cPtr->nextPtr) {
+ cPtr->nextPtr->prevPtr = cPtr->prevPtr;
+ } else {
+ dict->entryChainTail = cPtr->prevPtr;
+ }
+ if (cPtr->prevPtr) {
+ cPtr->prevPtr->nextPtr = cPtr->nextPtr;
+ } else {
+ dict->entryChainHead = cPtr->nextPtr;
+ }
+
+ Tcl_DeleteHashEntry(&cPtr->entry);
+ return 1;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * DupDictInternalRep --
+ *
+ * Initialize the internal representation of a dictionary Tcl_Obj to a
+ * copy of the internal representation of an existing dictionary object.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * "srcPtr"s dictionary internal rep pointer should not be NULL and we
+ * assume it is not NULL. We set "copyPtr"s internal rep to a pointer to
+ * a newly allocated dictionary rep that, in turn, points to "srcPtr"s
+ * key and value objects. Those objects are not actually copied but are
+ * shared between "srcPtr" and "copyPtr". The ref count of each key and
+ * value object is incremented.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+DupDictInternalRep(
+ Tcl_Obj *srcPtr,
+ Tcl_Obj *copyPtr)
+{
+ Dict *oldDict = srcPtr->internalRep.otherValuePtr;
+ Dict *newDict = (Dict *) ckalloc(sizeof(Dict));
+ ChainEntry *cPtr;
+
+ /*
+ * Copy values across from the old hash table.
+ */
+
+ InitChainTable(newDict);
+ for (cPtr=oldDict->entryChainHead ; cPtr!=NULL ; cPtr=cPtr->nextPtr) {
+ void *key = Tcl_GetHashKey(&oldDict->table, &cPtr->entry);
+ Tcl_Obj *valuePtr = Tcl_GetHashValue(&cPtr->entry);
+ int n;
+ Tcl_HashEntry *hPtr = CreateChainEntry(newDict, key, &n);
+
+ /*
+ * Fill in the contents.
+ */
+
+ Tcl_SetHashValue(hPtr, (ClientData) valuePtr);
+ Tcl_IncrRefCount(valuePtr);
+ }
+
+ /*
+ * Initialise other fields.
+ */
+
+ newDict->epoch = 0;
+ newDict->chain = NULL;
+ newDict->refcount = 1;
+
+ /*
+ * Store in the object.
+ */
+
+ copyPtr->internalRep.otherValuePtr = newDict;
+ copyPtr->typePtr = &tclDictType;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * FreeDictInternalRep --
+ *
+ * Deallocate the storage associated with a dictionary object's internal
+ * representation.
+ *
+ * Results:
+ * None
+ *
+ * Side effects:
+ * Frees the memory holding the dictionary's internal hash table unless
+ * it is locked by an iteration going over it.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+FreeDictInternalRep(
+ Tcl_Obj *dictPtr)
+{
+ Dict *dict = dictPtr->internalRep.otherValuePtr;
+
+ --dict->refcount;
+ if (dict->refcount <= 0) {
+ DeleteDict(dict);
+ }
+
+ dictPtr->internalRep.otherValuePtr = NULL; /* Belt and braces! */
+ dictPtr->typePtr = NULL;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * DeleteDict --
+ *
+ * Delete the structure that is used to implement a dictionary's internal
+ * representation. Called when either the dictionary object loses its
+ * internal representation or when the last iteration over the dictionary
+ * completes.
+ *
+ * Results:
+ * None
+ *
+ * Side effects:
+ * Decrements the reference count of all key and value objects in the
+ * dictionary, which may free them.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+DeleteDict(
+ Dict *dict)
+{
+ DeleteChainTable(dict);
+ ckfree((char *) dict);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * UpdateStringOfDict --
+ *
+ * Update the string representation for a dictionary object. Note: This
+ * function does not invalidate an existing old string rep so storage
+ * will be lost if this has not already been done. This code is based on
+ * UpdateStringOfList in tclListObj.c
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * The object's string is set to a valid string that results from the
+ * dict-to-string conversion. This string will be empty if the dictionary
+ * has no key/value pairs. The dictionary internal representation should
+ * not be NULL and we assume it is not NULL.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+UpdateStringOfDict(
+ Tcl_Obj *dictPtr)
+{
+#define LOCAL_SIZE 20
+ int localFlags[LOCAL_SIZE], *flagPtr = NULL;
+ Dict *dict = dictPtr->internalRep.otherValuePtr;
+ ChainEntry *cPtr;
+ Tcl_Obj *keyPtr, *valuePtr;
+ int i, length, bytesNeeded = 0;
+ char *elem, *dst;
+ const int maxFlags = UINT_MAX / sizeof(int);
+
+ /*
+ * This field is the most useful one in the whole hash structure, and it
+ * is not exposed by any API function...
+ */
+
+ int numElems = dict->table.numEntries * 2;
+
+ /* Handle empty list case first, simplifies what follows */
+ if (numElems == 0) {
+ dictPtr->bytes = tclEmptyStringRep;
+ dictPtr->length = 0;
+ return;
+ }
+
+ /*
+ * Pass 1: estimate space, gather flags.
+ */
+
+ if (numElems <= LOCAL_SIZE) {
+ flagPtr = localFlags;
+ } else if (numElems > maxFlags) {
+ Tcl_Panic("max size for a Tcl value (%d bytes) exceeded", INT_MAX);
+ } else {
+ flagPtr = (int *) ckalloc((unsigned) numElems*sizeof(int));
+ }
+ for (i=0,cPtr=dict->entryChainHead; i<numElems; i+=2,cPtr=cPtr->nextPtr) {
+ /*
+ * Assume that cPtr is never NULL since we know the number of array
+ * elements already.
+ */
+
+ flagPtr[i] = ( i ? TCL_DONT_QUOTE_HASH : 0 );
+ keyPtr = (Tcl_Obj *) Tcl_GetHashKey(&dict->table, &cPtr->entry);
+ elem = TclGetStringFromObj(keyPtr, &length);
+ bytesNeeded += TclScanElement(elem, length, flagPtr+i);
+ if (bytesNeeded < 0) {
+ Tcl_Panic("max size for a Tcl value (%d bytes) exceeded", INT_MAX);
+ }
+
+ flagPtr[i+1] = TCL_DONT_QUOTE_HASH;
+ valuePtr = Tcl_GetHashValue(&cPtr->entry);
+ elem = TclGetStringFromObj(valuePtr, &length);
+ bytesNeeded += TclScanElement(elem, length, flagPtr+i+1);
+ if (bytesNeeded < 0) {
+ Tcl_Panic("max size for a Tcl value (%d bytes) exceeded", INT_MAX);
+ }
+ }
+ if (bytesNeeded > INT_MAX - numElems + 1) {
+ Tcl_Panic("max size for a Tcl value (%d bytes) exceeded", INT_MAX);
+ }
+ bytesNeeded += numElems;
+
+ /*
+ * Pass 2: copy into string rep buffer.
+ */
+
+ dictPtr->length = bytesNeeded - 1;
+ dictPtr->bytes = ckalloc((unsigned) bytesNeeded);
+ dst = dictPtr->bytes;
+ for (i=0,cPtr=dict->entryChainHead; i<numElems; i+=2,cPtr=cPtr->nextPtr) {
+ flagPtr[i] |= ( i ? TCL_DONT_QUOTE_HASH : 0 );
+ keyPtr = (Tcl_Obj *) Tcl_GetHashKey(&dict->table, &cPtr->entry);
+ elem = TclGetStringFromObj(keyPtr, &length);
+ dst += TclConvertElement(elem, length, dst, flagPtr[i]);
+ *dst++ = ' ';
+
+ flagPtr[i+1] |= TCL_DONT_QUOTE_HASH;
+ valuePtr = Tcl_GetHashValue(&cPtr->entry);
+ elem = TclGetStringFromObj(valuePtr, &length);
+ dst += TclConvertElement(elem, length, dst, flagPtr[i+1]);
+ *dst++ = ' ';
+ }
+ dictPtr->bytes[dictPtr->length] = '\0';
+
+ if (flagPtr != localFlags) {
+ ckfree((char *) flagPtr);
+ }
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * SetDictFromAny --
+ *
+ * Convert a non-dictionary object into a dictionary object. This code is
+ * very closely related to SetListFromAny in tclListObj.c but does not
+ * actually guarantee that a dictionary object will have a string rep (as
+ * conversions from lists are handled with a special case.)
+ *
+ * Results:
+ * A standard Tcl result.
+ *
+ * Side effects:
+ * If the string can be converted, it loses any old internal
+ * representation that it had and gains a dictionary's internalRep.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+SetDictFromAny(
+ Tcl_Interp *interp,
+ Tcl_Obj *objPtr)
+{
+ Tcl_HashEntry *hPtr;
+ int isNew, result;
+ Dict *dict = (Dict *) ckalloc(sizeof(Dict));
+
+ InitChainTable(dict);
+
+ /*
+ * Since lists and dictionaries have very closely-related string
+ * representations (i.e. the same parsing code) we can safely special-case
+ * the conversion from lists to dictionaries.
+ */
+
+ if (objPtr->typePtr == &tclListType) {
+ int objc, i;
+ Tcl_Obj **objv;
+
+ /* Cannot fail, we already know the Tcl_ObjType is "list". */
+ TclListObjGetElements(NULL, objPtr, &objc, &objv);
+ if (objc & 1) {
+ goto missingValue;
+ }
+
+ for (i=0 ; i<objc ; i+=2) {
+
+ /* Store key and value in the hash table we're building. */
+ hPtr = CreateChainEntry(dict, objv[i], &isNew);
+ if (!isNew) {
+ Tcl_Obj *discardedValue = Tcl_GetHashValue(hPtr);
+
+ /*
+ * Not really a well-formed dictionary as there are duplicate
+ * keys, so better get the string rep here so that we can
+ * convert back.
+ */
+
+ (void) Tcl_GetString(objPtr);
+
+ TclDecrRefCount(discardedValue);
+ }
+ Tcl_SetHashValue(hPtr, objv[i+1]);
+ Tcl_IncrRefCount(objv[i+1]); /* Since hash now holds ref to it */
+ }
+ } else {
+ int length;
+ const char *nextElem = TclGetStringFromObj(objPtr, &length);
+ const char *limit = (nextElem + length);
+
+ while (nextElem < limit) {
+ Tcl_Obj *keyPtr, *valuePtr;
+ const char *elemStart;
+ int elemSize, literal;
+
+ result = TclFindElement(interp, nextElem, (limit - nextElem),
+ &elemStart, &nextElem, &elemSize, &literal);
+ if (result != TCL_OK) {
+ goto errorExit;
+ }
+ if (elemStart == limit) {
+ break;
+ }
+ if (nextElem == limit) {
+ goto missingValue;
+ }
+
+ if (literal) {
+ TclNewStringObj(keyPtr, elemStart, elemSize);
+ } else {
+ /* Avoid double copy */
+ TclNewObj(keyPtr);
+ keyPtr->bytes = ckalloc((unsigned) elemSize + 1);
+ keyPtr->length = TclCopyAndCollapse(elemSize, elemStart,
+ keyPtr->bytes);
+ }
+
+ result = TclFindElement(interp, nextElem, (limit - nextElem),
+ &elemStart, &nextElem, &elemSize, &literal);
+ if (result != TCL_OK) {
+ TclDecrRefCount(keyPtr);
+ goto errorExit;
+ }
+
+ if (literal) {
+ TclNewStringObj(valuePtr, elemStart, elemSize);
+ } else {
+ /* Avoid double copy */
+ TclNewObj(valuePtr);
+ valuePtr->bytes = ckalloc((unsigned) elemSize + 1);
+ valuePtr->length = TclCopyAndCollapse(elemSize, elemStart,
+ valuePtr->bytes);
+ }
+
+ /* Store key and value in the hash table we're building. */
+ hPtr = CreateChainEntry(dict, keyPtr, &isNew);
+ if (!isNew) {
+ Tcl_Obj *discardedValue = Tcl_GetHashValue(hPtr);
+
+ TclDecrRefCount(keyPtr);
+ TclDecrRefCount(discardedValue);
+ }
+ Tcl_SetHashValue(hPtr, valuePtr);
+ Tcl_IncrRefCount(valuePtr); /* since hash now holds ref to it */
+ }
+ }
+
+ /*
+ * Free the old internalRep before setting the new one. We do this as late
+ * as possible to allow the conversion code, in particular
+ * Tcl_GetStringFromObj, to use that old internalRep.
+ */
+
+ TclFreeIntRep(objPtr);
+ dict->epoch = 0;
+ dict->chain = NULL;
+ dict->refcount = 1;
+ objPtr->internalRep.otherValuePtr = dict;
+ objPtr->typePtr = &tclDictType;
+ return TCL_OK;
+
+ missingValue:
+ if (interp != NULL) {
+ Tcl_SetResult(interp, "missing value to go with key", TCL_STATIC);
+ }
+ result = TCL_ERROR;
+
+ errorExit:
+ DeleteChainTable(dict);
+ ckfree((char *) dict);
+ return result;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TclTraceDictPath --
+ *
+ * Trace through a tree of dictionaries using the array of keys given. If
+ * the flags argument has the DICT_PATH_UPDATE flag is set, a
+ * backward-pointing chain of dictionaries is also built (in the Dict's
+ * chain field) and the chained dictionaries are made into unshared
+ * dictionaries (if they aren't already.)
+ *
+ * Results:
+ * The object at the end of the path, or NULL if there was an error. Note
+ * that this it is an error for an intermediate dictionary on the path to
+ * not exist. If the flags argument has the DICT_PATH_EXISTS set, a
+ * non-existent path gives a DICT_PATH_NON_EXISTENT result.
+ *
+ * Side effects:
+ * If the flags argument is zero or DICT_PATH_EXISTS, there are no side
+ * effects (other than potential conversion of objects to dictionaries.)
+ * If the flags argument is DICT_PATH_UPDATE, the following additional
+ * side effects occur. Shared dictionaries along the path are converted
+ * into unshared objects, and a backward-pointing chain is built using
+ * the chain fields of the dictionaries (for easy invalidation of string
+ * representations using InvalidateDictChain). If the flags argument has
+ * the DICT_PATH_CREATE bits set (and not the DICT_PATH_EXISTS bit),
+ * non-existant keys will be inserted with a value of an empty
+ * dictionary, resulting in the path being built.
+ *
+ *----------------------------------------------------------------------
+ */
+
+Tcl_Obj *
+TclTraceDictPath(
+ Tcl_Interp *interp,
+ Tcl_Obj *dictPtr,
+ int keyc,
+ Tcl_Obj *const keyv[],
+ int flags)
+{
+ Dict *dict, *newDict;
+ int i;
+
+ if (dictPtr->typePtr != &tclDictType) {
+ if (SetDictFromAny(interp, dictPtr) != TCL_OK) {
+ return NULL;
+ }
+ }
+ dict = dictPtr->internalRep.otherValuePtr;
+ if (flags & DICT_PATH_UPDATE) {
+ dict->chain = NULL;
+ }
+
+ for (i=0 ; i<keyc ; i++) {
+ Tcl_HashEntry *hPtr = Tcl_FindHashEntry(&dict->table, (char *)keyv[i]);
+ Tcl_Obj *tmpObj;
+
+ if (hPtr == NULL) {
+ int isNew; /* Dummy */
+
+ if (flags & DICT_PATH_EXISTS) {
+ return DICT_PATH_NON_EXISTENT;
+ }
+ if ((flags & DICT_PATH_CREATE) != DICT_PATH_CREATE) {
+ if (interp != NULL) {
+ Tcl_ResetResult(interp);
+ Tcl_AppendResult(interp, "key \"", TclGetString(keyv[i]),
+ "\" not known in dictionary", NULL);
+ Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "DICT",
+ TclGetString(keyv[i]), NULL);
+ }
+ return NULL;
+ }
+
+ /*
+ * The next line should always set isNew to 1.
+ */
+
+ hPtr = CreateChainEntry(dict, keyv[i], &isNew);
+ tmpObj = Tcl_NewDictObj();
+ Tcl_IncrRefCount(tmpObj);
+ Tcl_SetHashValue(hPtr, tmpObj);
+ } else {
+ tmpObj = Tcl_GetHashValue(hPtr);
+ if (tmpObj->typePtr != &tclDictType) {
+ if (SetDictFromAny(interp, tmpObj) != TCL_OK) {
+ return NULL;
+ }
+ }
+ }
+
+ newDict = tmpObj->internalRep.otherValuePtr;
+ if (flags & DICT_PATH_UPDATE) {
+ if (Tcl_IsShared(tmpObj)) {
+ TclDecrRefCount(tmpObj);
+ tmpObj = Tcl_DuplicateObj(tmpObj);
+ Tcl_IncrRefCount(tmpObj);
+ Tcl_SetHashValue(hPtr, (ClientData) tmpObj);
+ dict->epoch++;
+ newDict = tmpObj->internalRep.otherValuePtr;
+ }
+
+ newDict->chain = dictPtr;
+ }
+ dict = newDict;
+ dictPtr = tmpObj;
+ }
+ return dictPtr;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * InvalidateDictChain --
+ *
+ * Go through a dictionary chain (built by an updating invokation of
+ * TclTraceDictPath) and invalidate the string representations of all the
+ * dictionaries on the chain.
+ *
+ * Results:
+ * None
+ *
+ * Side effects:
+ * String reps are invalidated and epoch counters (for detecting illegal
+ * concurrent modifications) are updated through the chain of updated
+ * dictionaries.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+InvalidateDictChain(
+ Tcl_Obj *dictObj)
+{
+ Dict *dict = dictObj->internalRep.otherValuePtr;
+
+ do {
+ Tcl_InvalidateStringRep(dictObj);
+ dict->epoch++;
+ dictObj = dict->chain;
+ if (dictObj == NULL) {
+ break;
+ }
+ dict->chain = NULL;
+ dict = dictObj->internalRep.otherValuePtr;
+ } while (dict != NULL);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * Tcl_DictObjPut --
+ *
+ * Add a key,value pair to a dictionary, or update the value for a key if
+ * that key already has a mapping in the dictionary.
+ *
+ * Results:
+ * A standard Tcl result.
+ *
+ * Side effects:
+ * The object pointed to by dictPtr is converted to a dictionary if it is
+ * not already one, and any string representation that it has is
+ * invalidated.
+ *
+ *----------------------------------------------------------------------
+ */
+
+int
+Tcl_DictObjPut(
+ Tcl_Interp *interp,
+ Tcl_Obj *dictPtr,
+ Tcl_Obj *keyPtr,
+ Tcl_Obj *valuePtr)
+{
+ Dict *dict;
+ Tcl_HashEntry *hPtr;
+ int isNew;
+
+ if (Tcl_IsShared(dictPtr)) {
+ Tcl_Panic("%s called with shared object", "Tcl_DictObjPut");
+ }
+
+ if (dictPtr->typePtr != &tclDictType) {
+ int result = SetDictFromAny(interp, dictPtr);
+
+ if (result != TCL_OK) {
+ return result;
+ }
+ }
+
+ if (dictPtr->bytes != NULL) {
+ Tcl_InvalidateStringRep(dictPtr);
+ }
+ dict = dictPtr->internalRep.otherValuePtr;
+ hPtr = CreateChainEntry(dict, keyPtr, &isNew);
+ Tcl_IncrRefCount(valuePtr);
+ if (!isNew) {
+ Tcl_Obj *oldValuePtr = Tcl_GetHashValue(hPtr);
+
+ TclDecrRefCount(oldValuePtr);
+ }
+ Tcl_SetHashValue(hPtr, valuePtr);
+ dict->epoch++;
+ return TCL_OK;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * Tcl_DictObjGet --
+ *
+ * Given a key, get its value from the dictionary (or NULL if key is not
+ * found in dictionary.)
+ *
+ * Results:
+ * A standard Tcl result. The variable pointed to by valuePtrPtr is
+ * updated with the value for the key. Note that it is not an error for
+ * the key to have no mapping in the dictionary.
+ *
+ * Side effects:
+ * The object pointed to by dictPtr is converted to a dictionary if it is
+ * not already one.
+ *
+ *----------------------------------------------------------------------
+ */
+
+int
+Tcl_DictObjGet(
+ Tcl_Interp *interp,
+ Tcl_Obj *dictPtr,
+ Tcl_Obj *keyPtr,
+ Tcl_Obj **valuePtrPtr)
+{
+ Dict *dict;
+ Tcl_HashEntry *hPtr;
+
+ if (dictPtr->typePtr != &tclDictType) {
+ int result = SetDictFromAny(interp, dictPtr);
+ if (result != TCL_OK) {
+ return result;
+ }
+ }
+
+ dict = dictPtr->internalRep.otherValuePtr;
+ hPtr = Tcl_FindHashEntry(&dict->table, (char *) keyPtr);
+ if (hPtr == NULL) {
+ *valuePtrPtr = NULL;
+ } else {
+ *valuePtrPtr = Tcl_GetHashValue(hPtr);
+ }
+ return TCL_OK;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * Tcl_DictObjRemove --
+ *
+ * Remove the key,value pair with the given key from the dictionary; the
+ * key does not need to be present in the dictionary.
+ *
+ * Results:
+ * A standard Tcl result.
+ *
+ * Side effects:
+ * The object pointed to by dictPtr is converted to a dictionary if it is
+ * not already one, and any string representation that it has is
+ * invalidated.
+ *
+ *----------------------------------------------------------------------
+ */
+
+int
+Tcl_DictObjRemove(
+ Tcl_Interp *interp,
+ Tcl_Obj *dictPtr,
+ Tcl_Obj *keyPtr)
+{
+ Dict *dict;
+
+ if (Tcl_IsShared(dictPtr)) {
+ Tcl_Panic("%s called with shared object", "Tcl_DictObjRemove");
+ }
+
+ if (dictPtr->typePtr != &tclDictType) {
+ int result = SetDictFromAny(interp, dictPtr);
+ if (result != TCL_OK) {
+ return result;
+ }
+ }
+
+ if (dictPtr->bytes != NULL) {
+ Tcl_InvalidateStringRep(dictPtr);
+ }
+ dict = dictPtr->internalRep.otherValuePtr;
+ if (DeleteChainEntry(dict, keyPtr)) {
+ dict->epoch++;
+ }
+ return TCL_OK;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * Tcl_DictObjSize --
+ *
+ * How many key,value pairs are there in the dictionary?
+ *
+ * Results:
+ * A standard Tcl result. Updates the variable pointed to by sizePtr with
+ * the number of key,value pairs in the dictionary.
+ *
+ * Side effects:
+ * The dictPtr object is converted to a dictionary type if it is not a
+ * dictionary already.
+ *
+ *----------------------------------------------------------------------
+ */
+
+int
+Tcl_DictObjSize(
+ Tcl_Interp *interp,
+ Tcl_Obj *dictPtr,
+ int *sizePtr)
+{
+ Dict *dict;
+
+ if (dictPtr->typePtr != &tclDictType) {
+ int result = SetDictFromAny(interp, dictPtr);
+ if (result != TCL_OK) {
+ return result;
+ }
+ }
+
+ dict = dictPtr->internalRep.otherValuePtr;
+ *sizePtr = dict->table.numEntries;
+ return TCL_OK;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * Tcl_DictObjFirst --
+ *
+ * Start a traversal of the dictionary. Caller must supply the search
+ * context, pointers for returning key and value, and a pointer to allow
+ * indication of whether the dictionary has been traversed (i.e. the
+ * dictionary is empty). The order of traversal is undefined.
+ *
+ * Results:
+ * A standard Tcl result. Updates the variables pointed to by keyPtrPtr,
+ * valuePtrPtr and donePtr. Either of keyPtrPtr and valuePtrPtr may be
+ * NULL, in which case the key/value is not made available to the caller.
+ *
+ * Side effects:
+ * The dictPtr object is converted to a dictionary type if it is not a
+ * dictionary already. The search context is initialised if the search
+ * has not finished. The dictionary's internal rep is Tcl_Preserve()d if
+ * the dictionary has at least one element.
+ *
+ *----------------------------------------------------------------------
+ */
+
+int
+Tcl_DictObjFirst(
+ Tcl_Interp *interp, /* For error messages, or NULL if no error
+ * messages desired. */
+ Tcl_Obj *dictPtr, /* Dictionary to traverse. */
+ Tcl_DictSearch *searchPtr, /* Pointer to a dict search context. */
+ Tcl_Obj **keyPtrPtr, /* Pointer to a variable to have the first key
+ * written into, or NULL. */
+ Tcl_Obj **valuePtrPtr, /* Pointer to a variable to have the first
+ * value written into, or NULL.*/
+ int *donePtr) /* Pointer to a variable which will have a 1
+ * written into when there are no further
+ * values in the dictionary, or a 0
+ * otherwise. */
+{
+ Dict *dict;
+ ChainEntry *cPtr;
+
+ if (dictPtr->typePtr != &tclDictType) {
+ int result = SetDictFromAny(interp, dictPtr);
+
+ if (result != TCL_OK) {
+ return result;
+ }
+ }
+
+ dict = dictPtr->internalRep.otherValuePtr;
+ cPtr = dict->entryChainHead;
+ if (cPtr == NULL) {
+ searchPtr->epoch = -1;
+ *donePtr = 1;
+ } else {
+ *donePtr = 0;
+ searchPtr->dictionaryPtr = (Tcl_Dict) dict;
+ searchPtr->epoch = dict->epoch;
+ searchPtr->next = cPtr->nextPtr;
+ dict->refcount++;
+ if (keyPtrPtr != NULL) {
+ *keyPtrPtr = (Tcl_Obj *) Tcl_GetHashKey(&dict->table,
+ &cPtr->entry);
+ }
+ if (valuePtrPtr != NULL) {
+ *valuePtrPtr = Tcl_GetHashValue(&cPtr->entry);
+ }
+ }
+ return TCL_OK;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * Tcl_DictObjNext --
+ *
+ * Continue a traversal of a dictionary previously started with
+ * Tcl_DictObjFirst. This function is safe against concurrent
+ * modification of the underlying object (including type shimmering),
+ * treating such situations as if the search has terminated, though it is
+ * up to the caller to ensure that the object itself is not disposed
+ * until the search has finished. It is _not_ safe against modifications
+ * from other threads.
+ *
+ * Results:
+ * Updates the variables pointed to by keyPtrPtr, valuePtrPtr and
+ * donePtr. Either of keyPtrPtr and valuePtrPtr may be NULL, in which
+ * case the key/value is not made available to the caller.
+ *
+ * Side effects:
+ * Removes a reference to the dictionary's internal rep if the search
+ * terminates.
+ *
+ *----------------------------------------------------------------------
+ */
+
+void
+Tcl_DictObjNext(
+ Tcl_DictSearch *searchPtr, /* Pointer to a hash search context. */
+ Tcl_Obj **keyPtrPtr, /* Pointer to a variable to have the first key
+ * written into, or NULL. */
+ Tcl_Obj **valuePtrPtr, /* Pointer to a variable to have the first
+ * value written into, or NULL.*/
+ int *donePtr) /* Pointer to a variable which will have a 1
+ * written into when there are no further
+ * values in the dictionary, or a 0
+ * otherwise. */
+{
+ ChainEntry *cPtr;
+
+ /*
+ * If the searh is done; we do no work.
+ */
+
+ if (searchPtr->epoch == -1) {
+ *donePtr = 1;
+ return;
+ }
+
+ /*
+ * Bail out if the dictionary has had any elements added, modified or
+ * removed. This *shouldn't* happen, but...
+ */
+
+ if (((Dict *)searchPtr->dictionaryPtr)->epoch != searchPtr->epoch) {
+ Tcl_Panic("concurrent dictionary modification and search");
+ }
+
+ cPtr = searchPtr->next;
+ if (cPtr == NULL) {
+ Tcl_DictObjDone(searchPtr);
+ *donePtr = 1;
+ return;
+ }
+
+ searchPtr->next = cPtr->nextPtr;
+ *donePtr = 0;
+ if (keyPtrPtr != NULL) {
+ *keyPtrPtr = (Tcl_Obj *) Tcl_GetHashKey(
+ &((Dict *)searchPtr->dictionaryPtr)->table, &cPtr->entry);
+ }
+ if (valuePtrPtr != NULL) {
+ *valuePtrPtr = Tcl_GetHashValue(&cPtr->entry);
+ }
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * Tcl_DictObjDone --
+ *
+ * Call this if you want to stop a search before you reach the end of the
+ * dictionary (e.g. because of abnormal termination of the search). It
+ * need not be used if the search reaches its natural end (i.e. if either
+ * Tcl_DictObjFirst or Tcl_DictObjNext sets its donePtr variable to 1).
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Removes a reference to the dictionary's internal rep.
+ *
+ *----------------------------------------------------------------------
+ */
+
+void
+Tcl_DictObjDone(
+ Tcl_DictSearch *searchPtr) /* Pointer to a hash search context. */
+{
+ Dict *dict;
+
+ if (searchPtr->epoch != -1) {
+ searchPtr->epoch = -1;
+ dict = (Dict *) searchPtr->dictionaryPtr;
+ dict->refcount--;
+ if (dict->refcount <= 0) {
+ DeleteDict(dict);
+ }
+ }
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * Tcl_DictObjPutKeyList --
+ *
+ * Add a key...key,value pair to a dictionary tree. The main dictionary
+ * value must not be shared, though sub-dictionaries may be. All
+ * intermediate dictionaries on the path must exist.
+ *
+ * Results:
+ * A standard Tcl result. Note that in the error case, a message is left
+ * in interp unless that is NULL.
+ *
+ * Side effects:
+ * If the dictionary and any of its sub-dictionaries on the path have
+ * string representations, these are invalidated.
+ *
+ *----------------------------------------------------------------------
+ */
+
+int
+Tcl_DictObjPutKeyList(
+ Tcl_Interp *interp,
+ Tcl_Obj *dictPtr,
+ int keyc,
+ Tcl_Obj *const keyv[],
+ Tcl_Obj *valuePtr)
+{
+ Dict *dict;
+ Tcl_HashEntry *hPtr;
+ int isNew;
+
+ if (Tcl_IsShared(dictPtr)) {
+ Tcl_Panic("%s called with shared object", "Tcl_DictObjPutKeyList");
+ }
+ if (keyc < 1) {
+ Tcl_Panic("%s called with empty key list", "Tcl_DictObjPutKeyList");
+ }
+
+ dictPtr = TclTraceDictPath(interp, dictPtr, keyc-1,keyv, DICT_PATH_CREATE);
+ if (dictPtr == NULL) {
+ return TCL_ERROR;
+ }
+
+ dict = dictPtr->internalRep.otherValuePtr;
+ hPtr = CreateChainEntry(dict, keyv[keyc-1], &isNew);
+ Tcl_IncrRefCount(valuePtr);
+ if (!isNew) {
+ Tcl_Obj *oldValuePtr = Tcl_GetHashValue(hPtr);
+ TclDecrRefCount(oldValuePtr);
+ }
+ Tcl_SetHashValue(hPtr, valuePtr);
+ InvalidateDictChain(dictPtr);
+
+ return TCL_OK;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * Tcl_DictObjRemoveKeyList --
+ *
+ * Remove a key...key,value pair from a dictionary tree (the value
+ * removed is implicit in the key path). The main dictionary value must
+ * not be shared, though sub-dictionaries may be. It is not an error if
+ * there is no value associated with the given key list, but all
+ * intermediate dictionaries on the key path must exist.
+ *
+ * Results:
+ * A standard Tcl result. Note that in the error case, a message is left
+ * in interp unless that is NULL.
+ *
+ * Side effects:
+ * If the dictionary and any of its sub-dictionaries on the key path have
+ * string representations, these are invalidated.
+ *
+ *----------------------------------------------------------------------
+ */
+
+int
+Tcl_DictObjRemoveKeyList(
+ Tcl_Interp *interp,
+ Tcl_Obj *dictPtr,
+ int keyc,
+ Tcl_Obj *const keyv[])
+{
+ Dict *dict;
+
+ if (Tcl_IsShared(dictPtr)) {
+ Tcl_Panic("%s called with shared object", "Tcl_DictObjRemoveKeyList");
+ }
+ if (keyc < 1) {
+ Tcl_Panic("%s called with empty key list", "Tcl_DictObjRemoveKeyList");
+ }
+
+ dictPtr = TclTraceDictPath(interp, dictPtr, keyc-1,keyv, DICT_PATH_UPDATE);
+ if (dictPtr == NULL) {
+ return TCL_ERROR;
+ }
+
+ dict = dictPtr->internalRep.otherValuePtr;
+ DeleteChainEntry(dict, keyv[keyc-1]);
+ InvalidateDictChain(dictPtr);
+ return TCL_OK;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * Tcl_NewDictObj --
+ *
+ * This function is normally called when not debugging: i.e., when
+ * TCL_MEM_DEBUG is not defined. It creates a new dict object without any
+ * content.
+ *
+ * When TCL_MEM_DEBUG is defined, this function just returns the result
+ * of calling the debugging version Tcl_DbNewDictObj.
+ *
+ * Results:
+ * A new dict object is returned; it has no keys defined in it. The new
+ * object's string representation is left NULL, and the ref count of the
+ * object is 0.
+ *
+ * Side Effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+Tcl_Obj *
+Tcl_NewDictObj(void)
+{
+#ifdef TCL_MEM_DEBUG
+ return Tcl_DbNewDictObj("unknown", 0);
+#else /* !TCL_MEM_DEBUG */
+
+ Tcl_Obj *dictPtr;
+ Dict *dict;
+
+ TclNewObj(dictPtr);
+ Tcl_InvalidateStringRep(dictPtr);
+ dict = (Dict *) ckalloc(sizeof(Dict));
+ InitChainTable(dict);
+ dict->epoch = 0;
+ dict->chain = NULL;
+ dict->refcount = 1;
+ dictPtr->internalRep.otherValuePtr = dict;
+ dictPtr->typePtr = &tclDictType;
+ return dictPtr;
+#endif
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * Tcl_DbNewDictObj --
+ *
+ * This function is normally called when debugging: i.e., when
+ * TCL_MEM_DEBUG is defined. It creates new dict objects. It is the same
+ * as the Tcl_NewDictObj function above except that it calls
+ * Tcl_DbCkalloc directly with the file name and line number from its
+ * caller. This simplifies debugging since then the [memory active]
+ * command will report the correct file name and line number when
+ * reporting objects that haven't been freed.
+ *
+ * When TCL_MEM_DEBUG is not defined, this function just returns the
+ * result of calling Tcl_NewDictObj.
+ *
+ * Results:
+ * A new dict object is returned; it has no keys defined in it. The new
+ * object's string representation is left NULL, and the ref count of the
+ * object is 0.
+ *
+ * Side Effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+Tcl_Obj *
+Tcl_DbNewDictObj(
+ const char *file,
+ int line)
+{
+#ifdef TCL_MEM_DEBUG
+ Tcl_Obj *dictPtr;
+ Dict *dict;
+
+ TclDbNewObj(dictPtr, file, line);
+ Tcl_InvalidateStringRep(dictPtr);
+ dict = (Dict *) ckalloc(sizeof(Dict));
+ InitChainTable(dict);
+ dict->epoch = 0;
+ dict->chain = NULL;
+ dict->refcount = 1;
+ dictPtr->internalRep.otherValuePtr = dict;
+ dictPtr->typePtr = &tclDictType;
+ return dictPtr;
+#else /* !TCL_MEM_DEBUG */
+ return Tcl_NewDictObj();
+#endif
+}
+
+/***** START OF FUNCTIONS IMPLEMENTING TCL COMMANDS *****/
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * DictCreateCmd --
+ *
+ * This function implements the "dict create" Tcl command. See the user
+ * documentation for details on what it does, and TIP#111 for the formal
+ * specification.
+ *
+ * Results:
+ * A standard Tcl result.
+ *
+ * Side effects:
+ * See the user documentation.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+DictCreateCmd(
+ ClientData dummy,
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *const *objv)
+{
+ Tcl_Obj *dictObj;
+ int i;
+
+ /*
+ * Must have an even number of arguments; note that number of preceding
+ * arguments (i.e. "dict create" is also even, which makes this much
+ * easier.)
+ */
+
+ if ((objc & 1) == 0) {
+ Tcl_WrongNumArgs(interp, 1, objv, "?key value ...?");
+ return TCL_ERROR;
+ }
+
+ dictObj = Tcl_NewDictObj();
+ for (i=1 ; i<objc ; i+=2) {
+ /*
+ * The next command is assumed to never fail...
+ */
+ Tcl_DictObjPut(interp, dictObj, objv[i], objv[i+1]);
+ }
+ Tcl_SetObjResult(interp, dictObj);
+ return TCL_OK;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * DictGetCmd --
+ *
+ * This function implements the "dict get" Tcl command. See the user
+ * documentation for details on what it does, and TIP#111 for the formal
+ * specification.
+ *
+ * Results:
+ * A standard Tcl result.
+ *
+ * Side effects:
+ * See the user documentation.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+DictGetCmd(
+ ClientData dummy,
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *const *objv)
+{
+ Tcl_Obj *dictPtr, *valuePtr = NULL;
+ int result;
+
+ if (objc < 2) {
+ Tcl_WrongNumArgs(interp, 1, objv, "dictionary ?key key ...?");
+ return TCL_ERROR;
+ }
+
+ /*
+ * Test for the special case of no keys, which returns a *list* of all
+ * key,value pairs. We produce a copy here because that makes subsequent
+ * list handling more efficient.
+ */
+
+ if (objc == 2) {
+ Tcl_Obj *keyPtr, *listPtr;
+ Tcl_DictSearch search;
+ int done;
+
+ result = Tcl_DictObjFirst(interp, objv[1], &search,
+ &keyPtr, &valuePtr, &done);
+ if (result != TCL_OK) {
+ return result;
+ }
+ listPtr = Tcl_NewListObj(0, NULL);
+ while (!done) {
+ /*
+ * Assume these won't fail as we have complete control over the
+ * types of things here.
+ */
+
+ Tcl_ListObjAppendElement(interp, listPtr, keyPtr);
+ Tcl_ListObjAppendElement(interp, listPtr, valuePtr);
+
+ Tcl_DictObjNext(&search, &keyPtr, &valuePtr, &done);
+ }
+ Tcl_SetObjResult(interp, listPtr);
+ return TCL_OK;
+ }
+
+ /*
+ * Loop through the list of keys, looking up the key at the current index
+ * in the current dictionary each time. Once we've done the lookup, we set
+ * the current dictionary to be the value we looked up (in case the value
+ * was not the last one and we are going through a chain of searches.)
+ * Note that this loop always executes at least once.
+ */
+
+ dictPtr = TclTraceDictPath(interp, objv[1], objc-3,objv+2, DICT_PATH_READ);
+ if (dictPtr == NULL) {
+ return TCL_ERROR;
+ }
+ result = Tcl_DictObjGet(interp, dictPtr, objv[objc-1], &valuePtr);
+ if (result != TCL_OK) {
+ return result;
+ }
+ if (valuePtr == NULL) {
+ Tcl_ResetResult(interp);
+ Tcl_AppendResult(interp, "key \"", TclGetString(objv[objc-1]),
+ "\" not known in dictionary", NULL);
+ return TCL_ERROR;
+ }
+ Tcl_SetObjResult(interp, valuePtr);
+ return TCL_OK;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * DictReplaceCmd --
+ *
+ * This function implements the "dict replace" Tcl command. See the user
+ * documentation for details on what it does, and TIP#111 for the formal
+ * specification.
+ *
+ * Results:
+ * A standard Tcl result.
+ *
+ * Side effects:
+ * See the user documentation.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+DictReplaceCmd(
+ ClientData dummy,
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *const *objv)
+{
+ Tcl_Obj *dictPtr;
+ int i, result;
+ int allocatedDict = 0;
+
+ if ((objc < 2) || (objc & 1)) {
+ Tcl_WrongNumArgs(interp, 1, objv, "dictionary ?key value ...?");
+ return TCL_ERROR;
+ }
+
+ dictPtr = objv[1];
+ if (Tcl_IsShared(dictPtr)) {
+ dictPtr = Tcl_DuplicateObj(dictPtr);
+ allocatedDict = 1;
+ }
+ for (i=2 ; i<objc ; i+=2) {
+ result = Tcl_DictObjPut(interp, dictPtr, objv[i], objv[i+1]);
+ if (result != TCL_OK) {
+ if (allocatedDict) {
+ TclDecrRefCount(dictPtr);
+ }
+ return TCL_ERROR;
+ }
+ }
+ Tcl_SetObjResult(interp, dictPtr);
+ return TCL_OK;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * DictRemoveCmd --
+ *
+ * This function implements the "dict remove" Tcl command. See the user
+ * documentation for details on what it does, and TIP#111 for the formal
+ * specification.
+ *
+ * Results:
+ * A standard Tcl result.
+ *
+ * Side effects:
+ * See the user documentation.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+DictRemoveCmd(
+ ClientData dummy,
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *const *objv)
+{
+ Tcl_Obj *dictPtr;
+ int i, result;
+ int allocatedDict = 0;
+
+ if (objc < 2) {
+ Tcl_WrongNumArgs(interp, 1, objv, "dictionary ?key ...?");
+ return TCL_ERROR;
+ }
+
+ dictPtr = objv[1];
+ if (Tcl_IsShared(dictPtr)) {
+ dictPtr = Tcl_DuplicateObj(dictPtr);
+ allocatedDict = 1;
+ }
+ for (i=2 ; i<objc ; i++) {
+ result = Tcl_DictObjRemove(interp, dictPtr, objv[i]);
+ if (result != TCL_OK) {
+ if (allocatedDict) {
+ TclDecrRefCount(dictPtr);
+ }
+ return TCL_ERROR;
+ }
+ }
+ Tcl_SetObjResult(interp, dictPtr);
+ return TCL_OK;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * DictMergeCmd --
+ *
+ * This function implements the "dict merge" Tcl command. See the user
+ * documentation for details on what it does, and TIP#163 for the formal
+ * specification.
+ *
+ * Results:
+ * A standard Tcl result.
+ *
+ * Side effects:
+ * See the user documentation.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+DictMergeCmd(
+ ClientData dummy,
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *const *objv)
+{
+ Tcl_Obj *targetObj, *keyObj, *valueObj;
+ int allocatedDict = 0;
+ int i, done;
+ Tcl_DictSearch search;
+
+ if (objc == 1) {
+ /*
+ * No dictionary arguments; return default (empty value).
+ */
+
+ return TCL_OK;
+ }
+
+ /*
+ * Make sure first argument is a dictionary.
+ */
+
+ targetObj = objv[1];
+ if (targetObj->typePtr != &tclDictType) {
+ if (SetDictFromAny(interp, targetObj) != TCL_OK) {
+ return TCL_ERROR;
+ }
+ }
+
+ if (objc == 2) {
+ /*
+ * Single argument, return it.
+ */
+
+ Tcl_SetObjResult(interp, objv[1]);
+ return TCL_OK;
+ }
+
+ /*
+ * Normal behaviour: combining two (or more) dictionaries.
+ */
+
+ if (Tcl_IsShared(targetObj)) {
+ targetObj = Tcl_DuplicateObj(targetObj);
+ allocatedDict = 1;
+ }
+ for (i=2 ; i<objc ; i++) {
+ if (Tcl_DictObjFirst(interp, objv[i], &search, &keyObj, &valueObj,
+ &done) != TCL_OK) {
+ if (allocatedDict) {
+ TclDecrRefCount(targetObj);
+ }
+ return TCL_ERROR;
+ }
+ while (!done) {
+ /*
+ * Next line can't fail; already know we have a dictionary in
+ * targetObj.
+ */
+
+ Tcl_DictObjPut(NULL, targetObj, keyObj, valueObj);
+ Tcl_DictObjNext(&search, &keyObj, &valueObj, &done);
+ }
+ Tcl_DictObjDone(&search);
+ }
+ Tcl_SetObjResult(interp, targetObj);
+ return TCL_OK;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * DictKeysCmd --
+ *
+ * This function implements the "dict keys" Tcl command. See the user
+ * documentation for details on what it does, and TIP#111 for the formal
+ * specification.
+ *
+ * Results:
+ * A standard Tcl result.
+ *
+ * Side effects:
+ * See the user documentation.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+DictKeysCmd(
+ ClientData dummy,
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *const *objv)
+{
+ Tcl_Obj *listPtr;
+ char *pattern = NULL;
+
+ if (objc!=2 && objc!=3) {
+ Tcl_WrongNumArgs(interp, 1, objv, "dictionary ?pattern?");
+ return TCL_ERROR;
+ }
+
+ /*
+ * A direct check that we have a dictionary. We don't start the iteration
+ * yet because that might allocate memory or set locks that we do not
+ * need. [Bug 1705778, leak K04]
+ */
+
+ if (objv[1]->typePtr != &tclDictType) {
+ int result = SetDictFromAny(interp, objv[1]);
+
+ if (result != TCL_OK) {
+ return result;
+ }
+ }
+
+ if (objc == 3) {
+ pattern = TclGetString(objv[2]);
+ }
+ listPtr = Tcl_NewListObj(0, NULL);
+ if ((pattern != NULL) && TclMatchIsTrivial(pattern)) {
+ Tcl_Obj *valuePtr = NULL;
+
+ Tcl_DictObjGet(interp, objv[1], objv[2], &valuePtr);
+ if (valuePtr != NULL) {
+ Tcl_ListObjAppendElement(NULL, listPtr, objv[2]);
+ }
+ } else {
+ Tcl_DictSearch search;
+ Tcl_Obj *keyPtr;
+ int done;
+
+ /*
+ * At this point, we know we have a dictionary (or at least something
+ * that can be represented; it could theoretically have shimmered away
+ * when the pattern was fetched, but that shouldn't be damaging) so we
+ * can start the iteration process without checking for failures.
+ */
+
+ Tcl_DictObjFirst(NULL, objv[1], &search, &keyPtr, NULL, &done);
+ for (; !done ; Tcl_DictObjNext(&search, &keyPtr, NULL, &done)) {
+ if (!pattern || Tcl_StringMatch(TclGetString(keyPtr), pattern)) {
+ Tcl_ListObjAppendElement(NULL, listPtr, keyPtr);
+ }
+ }
+ Tcl_DictObjDone(&search);
+ }
+
+ Tcl_SetObjResult(interp, listPtr);
+ return TCL_OK;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * DictValuesCmd --
+ *
+ * This function implements the "dict values" Tcl command. See the user
+ * documentation for details on what it does, and TIP#111 for the formal
+ * specification.
+ *
+ * Results:
+ * A standard Tcl result.
+ *
+ * Side effects:
+ * See the user documentation.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+DictValuesCmd(
+ ClientData dummy,
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *const *objv)
+{
+ Tcl_Obj *valuePtr, *listPtr;
+ Tcl_DictSearch search;
+ int done;
+ char *pattern;
+
+ if (objc!=2 && objc!=3) {
+ Tcl_WrongNumArgs(interp, 1, objv, "dictionary ?pattern?");
+ return TCL_ERROR;
+ }
+
+ if (Tcl_DictObjFirst(interp, objv[1], &search, NULL, &valuePtr,
+ &done) != TCL_OK) {
+ return TCL_ERROR;
+ }
+ if (objc == 3) {
+ pattern = TclGetString(objv[2]);
+ } else {
+ pattern = NULL;
+ }
+ listPtr = Tcl_NewListObj(0, NULL);
+ for (; !done ; Tcl_DictObjNext(&search, NULL, &valuePtr, &done)) {
+ if (pattern==NULL || Tcl_StringMatch(TclGetString(valuePtr),pattern)) {
+ /*
+ * Assume this operation always succeeds.
+ */
+
+ Tcl_ListObjAppendElement(interp, listPtr, valuePtr);
+ }
+ }
+ Tcl_DictObjDone(&search);
+
+ Tcl_SetObjResult(interp, listPtr);
+ return TCL_OK;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * DictSizeCmd --
+ *
+ * This function implements the "dict size" Tcl command. See the user
+ * documentation for details on what it does, and TIP#111 for the formal
+ * specification.
+ *
+ * Results:
+ * A standard Tcl result.
+ *
+ * Side effects:
+ * See the user documentation.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+DictSizeCmd(
+ ClientData dummy,
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *const *objv)
+{
+ int result, size;
+
+ if (objc != 2) {
+ Tcl_WrongNumArgs(interp, 1, objv, "dictionary");
+ return TCL_ERROR;
+ }
+ result = Tcl_DictObjSize(interp, objv[1], &size);
+ if (result == TCL_OK) {
+ Tcl_SetObjResult(interp, Tcl_NewIntObj(size));
+ }
+ return result;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * DictExistsCmd --
+ *
+ * This function implements the "dict exists" Tcl command. See the user
+ * documentation for details on what it does, and TIP#111 for the formal
+ * specification.
+ *
+ * Results:
+ * A standard Tcl result.
+ *
+ * Side effects:
+ * See the user documentation.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+DictExistsCmd(
+ ClientData dummy,
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *const *objv)
+{
+ Tcl_Obj *dictPtr, *valuePtr;
+
+ if (objc < 3) {
+ Tcl_WrongNumArgs(interp, 1, objv, "dictionary key ?key ...?");
+ return TCL_ERROR;
+ }
+
+ dictPtr = TclTraceDictPath(interp, objv[1], objc-3, objv+2,
+ DICT_PATH_EXISTS);
+ if (dictPtr == NULL || dictPtr == DICT_PATH_NON_EXISTENT
+ || Tcl_DictObjGet(interp, dictPtr, objv[objc-1],
+ &valuePtr) != TCL_OK) {
+ Tcl_SetObjResult(interp, Tcl_NewBooleanObj(0));
+ } else {
+ Tcl_SetObjResult(interp, Tcl_NewBooleanObj(valuePtr != NULL));
+ }
+ return TCL_OK;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * DictInfoCmd --
+ *
+ * This function implements the "dict info" Tcl command. See the user
+ * documentation for details on what it does, and TIP#111 for the formal
+ * specification.
+ *
+ * Results:
+ * A standard Tcl result.
+ *
+ * Side effects:
+ * See the user documentation.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+DictInfoCmd(
+ ClientData dummy,
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *const *objv)
+{
+ Tcl_Obj *dictPtr;
+ Dict *dict;
+
+ if (objc != 2) {
+ Tcl_WrongNumArgs(interp, 1, objv, "dictionary");
+ return TCL_ERROR;
+ }
+
+ dictPtr = objv[1];
+ if (dictPtr->typePtr != &tclDictType) {
+ int result = SetDictFromAny(interp, dictPtr);
+ if (result != TCL_OK) {
+ return result;
+ }
+ }
+ dict = dictPtr->internalRep.otherValuePtr;
+
+ /*
+ * This next cast is actually OK.
+ */
+
+ Tcl_SetResult(interp, (char *) Tcl_HashStats(&dict->table), TCL_DYNAMIC);
+ return TCL_OK;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * DictIncrCmd --
+ *
+ * This function implements the "dict incr" Tcl command. See the user
+ * documentation for details on what it does, and TIP#111 for the formal
+ * specification.
+ *
+ * Results:
+ * A standard Tcl result.
+ *
+ * Side effects:
+ * See the user documentation.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+DictIncrCmd(
+ ClientData dummy,
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *const *objv)
+{
+ int code = TCL_OK;
+ Tcl_Obj *dictPtr, *valuePtr = NULL;
+
+ if (objc < 3 || objc > 4) {
+ Tcl_WrongNumArgs(interp, 1, objv, "varName key ?increment?");
+ return TCL_ERROR;
+ }
+
+ dictPtr = Tcl_ObjGetVar2(interp, objv[1], NULL, 0);
+ if (dictPtr == NULL) {
+ /*
+ * Variable didn't yet exist. Create new dictionary value.
+ */
+
+ dictPtr = Tcl_NewDictObj();
+ } else if (Tcl_DictObjGet(interp, dictPtr, objv[2], &valuePtr) != TCL_OK) {
+ /*
+ * Variable contents are not a dict, report error.
+ */
+
+ return TCL_ERROR;
+ }
+ if (Tcl_IsShared(dictPtr)) {
+ /*
+ * A little internals surgery to avoid copying a string rep that will
+ * soon be no good.
+ */
+
+ char *saved = dictPtr->bytes;
+ Tcl_Obj *oldPtr = dictPtr;
+
+ dictPtr->bytes = NULL;
+ dictPtr = Tcl_DuplicateObj(dictPtr);
+ oldPtr->bytes = saved;
+ }
+ if (valuePtr == NULL) {
+ /*
+ * Key not in dictionary. Create new key with increment as value.
+ */
+
+ if (objc == 4) {
+ /*
+ * Verify increment is an integer.
+ */
+
+ mp_int increment;
+
+ code = Tcl_GetBignumFromObj(interp, objv[3], &increment);
+ if (code != TCL_OK) {
+ Tcl_AddErrorInfo(interp, "\n (reading increment)");
+ } else {
+ /*
+ * Remember to dispose with the bignum as we're not actually
+ * using it directly. [Bug 2874678]
+ */
+
+ mp_clear(&increment);
+ Tcl_DictObjPut(interp, dictPtr, objv[2], objv[3]);
+ }
+ } else {
+ Tcl_DictObjPut(interp, dictPtr, objv[2], Tcl_NewIntObj(1));
+ }
+ } else {
+ /*
+ * Key in dictionary. Increment its value with minimum dup.
+ */
+
+ if (Tcl_IsShared(valuePtr)) {
+ valuePtr = Tcl_DuplicateObj(valuePtr);
+ Tcl_DictObjPut(interp, dictPtr, objv[2], valuePtr);
+ }
+ if (objc == 4) {
+ code = TclIncrObj(interp, valuePtr, objv[3]);
+ } else {
+ Tcl_Obj *incrPtr = Tcl_NewIntObj(1);
+
+ Tcl_IncrRefCount(incrPtr);
+ code = TclIncrObj(interp, valuePtr, incrPtr);
+ Tcl_DecrRefCount(incrPtr);
+ }
+ }
+ if (code == TCL_OK) {
+ Tcl_InvalidateStringRep(dictPtr);
+ valuePtr = Tcl_ObjSetVar2(interp, objv[1], NULL,
+ dictPtr, TCL_LEAVE_ERR_MSG);
+ if (valuePtr == NULL) {
+ code = TCL_ERROR;
+ } else {
+ Tcl_SetObjResult(interp, valuePtr);
+ }
+ } else if (dictPtr->refCount == 0) {
+ Tcl_DecrRefCount(dictPtr);
+ }
+ return code;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * DictLappendCmd --
+ *
+ * This function implements the "dict lappend" Tcl command. See the user
+ * documentation for details on what it does, and TIP#111 for the formal
+ * specification.
+ *
+ * Results:
+ * A standard Tcl result.
+ *
+ * Side effects:
+ * See the user documentation.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+DictLappendCmd(
+ ClientData dummy,
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *const *objv)
+{
+ Tcl_Obj *dictPtr, *valuePtr, *resultPtr;
+ int i, allocatedDict = 0, allocatedValue = 0;
+
+ if (objc < 3) {
+ Tcl_WrongNumArgs(interp, 1, objv, "varName key ?value ...?");
+ return TCL_ERROR;
+ }
+
+ dictPtr = Tcl_ObjGetVar2(interp, objv[1], NULL, 0);
+ if (dictPtr == NULL) {
+ allocatedDict = 1;
+ dictPtr = Tcl_NewDictObj();
+ } else if (Tcl_IsShared(dictPtr)) {
+ allocatedDict = 1;
+ dictPtr = Tcl_DuplicateObj(dictPtr);
+ }
+
+ if (Tcl_DictObjGet(interp, dictPtr, objv[2], &valuePtr) != TCL_OK) {
+ if (allocatedDict) {
+ TclDecrRefCount(dictPtr);
+ }
+ return TCL_ERROR;
+ }
+
+ if (valuePtr == NULL) {
+ valuePtr = Tcl_NewListObj(objc-3, objv+3);
+ allocatedValue = 1;
+ } else {
+ if (Tcl_IsShared(valuePtr)) {
+ allocatedValue = 1;
+ valuePtr = Tcl_DuplicateObj(valuePtr);
+ }
+
+ for (i=3 ; i<objc ; i++) {
+ if (Tcl_ListObjAppendElement(interp, valuePtr,
+ objv[i]) != TCL_OK) {
+ if (allocatedValue) {
+ TclDecrRefCount(valuePtr);
+ }
+ if (allocatedDict) {
+ TclDecrRefCount(dictPtr);
+ }
+ return TCL_ERROR;
+ }
+ }
+ }
+
+ if (allocatedValue) {
+ Tcl_DictObjPut(interp, dictPtr, objv[2], valuePtr);
+ } else if (dictPtr->bytes != NULL) {
+ Tcl_InvalidateStringRep(dictPtr);
+ }
+
+ resultPtr = Tcl_ObjSetVar2(interp, objv[1], NULL, dictPtr,
+ TCL_LEAVE_ERR_MSG);
+ if (resultPtr == NULL) {
+ return TCL_ERROR;
+ }
+ Tcl_SetObjResult(interp, resultPtr);
+ return TCL_OK;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * DictAppendCmd --
+ *
+ * This function implements the "dict append" Tcl command. See the user
+ * documentation for details on what it does, and TIP#111 for the formal
+ * specification.
+ *
+ * Results:
+ * A standard Tcl result.
+ *
+ * Side effects:
+ * See the user documentation.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+DictAppendCmd(
+ ClientData dummy,
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *const *objv)
+{
+ Tcl_Obj *dictPtr, *valuePtr, *resultPtr;
+ int i, allocatedDict = 0;
+
+ if (objc < 3) {
+ Tcl_WrongNumArgs(interp, 1, objv, "varName key ?value ...?");
+ return TCL_ERROR;
+ }
+
+ dictPtr = Tcl_ObjGetVar2(interp, objv[1], NULL, 0);
+ if (dictPtr == NULL) {
+ allocatedDict = 1;
+ dictPtr = Tcl_NewDictObj();
+ } else if (Tcl_IsShared(dictPtr)) {
+ allocatedDict = 1;
+ dictPtr = Tcl_DuplicateObj(dictPtr);
+ }
+
+ if (Tcl_DictObjGet(interp, dictPtr, objv[2], &valuePtr) != TCL_OK) {
+ if (allocatedDict) {
+ TclDecrRefCount(dictPtr);
+ }
+ return TCL_ERROR;
+ }
+
+ if (valuePtr == NULL) {
+ TclNewObj(valuePtr);
+ } else {
+ if (Tcl_IsShared(valuePtr)) {
+ valuePtr = Tcl_DuplicateObj(valuePtr);
+ }
+ }
+
+ for (i=3 ; i<objc ; i++) {
+ Tcl_AppendObjToObj(valuePtr, objv[i]);
+ }
+
+ Tcl_DictObjPut(interp, dictPtr, objv[2], valuePtr);
+
+ resultPtr = Tcl_ObjSetVar2(interp, objv[1], NULL, dictPtr,
+ TCL_LEAVE_ERR_MSG);
+ if (resultPtr == NULL) {
+ return TCL_ERROR;
+ }
+ Tcl_SetObjResult(interp, resultPtr);
+ return TCL_OK;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * DictForCmd --
+ *
+ * This function implements the "dict for" Tcl command. See the user
+ * documentation for details on what it does, and TIP#111 for the formal
+ * specification.
+ *
+ * Results:
+ * A standard Tcl result.
+ *
+ * Side effects:
+ * See the user documentation.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+DictForCmd(
+ ClientData dummy,
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *const *objv)
+{
+ Interp *iPtr = (Interp *) interp;
+ Tcl_Obj *scriptObj, *keyVarObj, *valueVarObj;
+ Tcl_Obj **varv, *keyObj, *valueObj;
+ Tcl_DictSearch search;
+ int varc, done, result;
+
+ if (objc != 4) {
+ Tcl_WrongNumArgs(interp, 1, objv,
+ "{keyVar valueVar} dictionary script");
+ return TCL_ERROR;
+ }
+
+ if (TclListObjGetElements(interp, objv[1], &varc, &varv) != TCL_OK) {
+ return TCL_ERROR;
+ }
+ if (varc != 2) {
+ Tcl_SetResult(interp, "must have exactly two variable names",
+ TCL_STATIC);
+ return TCL_ERROR;
+ }
+ keyVarObj = varv[0];
+ valueVarObj = varv[1];
+ scriptObj = objv[3];
+
+ if (Tcl_DictObjFirst(interp, objv[2], &search, &keyObj, &valueObj,
+ &done) != TCL_OK) {
+ return TCL_ERROR;
+ }
+
+ /*
+ * Make sure that these objects (which we need throughout the body of the
+ * loop) don't vanish. Note that the dictionary internal rep is locked
+ * internally so that updates, shimmering, etc are not a problem.
+ */
+
+ Tcl_IncrRefCount(keyVarObj);
+ Tcl_IncrRefCount(valueVarObj);
+ Tcl_IncrRefCount(scriptObj);
+
+ result = TCL_OK;
+ while (!done) {
+ /*
+ * Stop the value from getting hit in any way by any traces on the key
+ * variable.
+ */
+
+ Tcl_IncrRefCount(valueObj);
+ if (Tcl_ObjSetVar2(interp, keyVarObj, NULL, keyObj, 0) == NULL) {
+ Tcl_ResetResult(interp);
+ Tcl_AppendResult(interp, "couldn't set key variable: \"",
+ TclGetString(keyVarObj), "\"", NULL);
+ TclDecrRefCount(valueObj);
+ result = TCL_ERROR;
+ break;
+ }
+ TclDecrRefCount(valueObj);
+ if (Tcl_ObjSetVar2(interp, valueVarObj, NULL, valueObj, 0) == NULL) {
+ Tcl_ResetResult(interp);
+ Tcl_AppendResult(interp, "couldn't set value variable: \"",
+ TclGetString(valueVarObj), "\"", NULL);
+ result = TCL_ERROR;
+ break;
+ }
+
+ /*
+ * TIP #280. Make invoking context available to loop body.
+ */
+
+ result = TclEvalObjEx(interp, scriptObj, 0, iPtr->cmdFramePtr, 3);
+ if (result == TCL_CONTINUE) {
+ result = TCL_OK;
+ } else if (result != TCL_OK) {
+ if (result == TCL_BREAK) {
+ result = TCL_OK;
+ } else if (result == TCL_ERROR) {
+ Tcl_AppendObjToErrorInfo(interp, Tcl_ObjPrintf(
+ "\n (\"dict for\" body line %d)",
+ interp->errorLine));
+ }
+ break;
+ }
+
+ Tcl_DictObjNext(&search, &keyObj, &valueObj, &done);
+ }
+
+ /*
+ * Stop holding a reference to these objects.
+ */
+
+ TclDecrRefCount(keyVarObj);
+ TclDecrRefCount(valueVarObj);
+ TclDecrRefCount(scriptObj);
+
+ Tcl_DictObjDone(&search);
+ if (result == TCL_OK) {
+ Tcl_ResetResult(interp);
+ }
+ return result;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * DictSetCmd --
+ *
+ * This function implements the "dict set" Tcl command. See the user
+ * documentation for details on what it does, and TIP#111 for the formal
+ * specification.
+ *
+ * Results:
+ * A standard Tcl result.
+ *
+ * Side effects:
+ * See the user documentation.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+DictSetCmd(
+ ClientData dummy,
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *const *objv)
+{
+ Tcl_Obj *dictPtr, *resultPtr;
+ int result, allocatedDict = 0;
+
+ if (objc < 4) {
+ Tcl_WrongNumArgs(interp, 1, objv, "varName key ?key ...? value");
+ return TCL_ERROR;
+ }
+
+ dictPtr = Tcl_ObjGetVar2(interp, objv[1], NULL, 0);
+ if (dictPtr == NULL) {
+ allocatedDict = 1;
+ dictPtr = Tcl_NewDictObj();
+ } else if (Tcl_IsShared(dictPtr)) {
+ allocatedDict = 1;
+ dictPtr = Tcl_DuplicateObj(dictPtr);
+ }
+
+ result = Tcl_DictObjPutKeyList(interp, dictPtr, objc-3, objv+2,
+ objv[objc-1]);
+ if (result != TCL_OK) {
+ if (allocatedDict) {
+ TclDecrRefCount(dictPtr);
+ }
+ return TCL_ERROR;
+ }
+
+ resultPtr = Tcl_ObjSetVar2(interp, objv[1], NULL, dictPtr,
+ TCL_LEAVE_ERR_MSG);
+ if (resultPtr == NULL) {
+ return TCL_ERROR;
+ }
+ Tcl_SetObjResult(interp, resultPtr);
+ return TCL_OK;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * DictUnsetCmd --
+ *
+ * This function implements the "dict unset" Tcl command. See the user
+ * documentation for details on what it does, and TIP#111 for the formal
+ * specification.
+ *
+ * Results:
+ * A standard Tcl result.
+ *
+ * Side effects:
+ * See the user documentation.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+DictUnsetCmd(
+ ClientData dummy,
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *const *objv)
+{
+ Tcl_Obj *dictPtr, *resultPtr;
+ int result, allocatedDict = 0;
+
+ if (objc < 3) {
+ Tcl_WrongNumArgs(interp, 1, objv, "varName key ?key ...?");
+ return TCL_ERROR;
+ }
+
+ dictPtr = Tcl_ObjGetVar2(interp, objv[1], NULL, 0);
+ if (dictPtr == NULL) {
+ allocatedDict = 1;
+ dictPtr = Tcl_NewDictObj();
+ } else if (Tcl_IsShared(dictPtr)) {
+ allocatedDict = 1;
+ dictPtr = Tcl_DuplicateObj(dictPtr);
+ }
+
+ result = Tcl_DictObjRemoveKeyList(interp, dictPtr, objc-2, objv+2);
+ if (result != TCL_OK) {
+ if (allocatedDict) {
+ TclDecrRefCount(dictPtr);
+ }
+ return TCL_ERROR;
+ }
+
+ resultPtr = Tcl_ObjSetVar2(interp, objv[1], NULL, dictPtr,
+ TCL_LEAVE_ERR_MSG);
+ if (resultPtr == NULL) {
+ return TCL_ERROR;
+ }
+ Tcl_SetObjResult(interp, resultPtr);
+ return TCL_OK;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * DictFilterCmd --
+ *
+ * This function implements the "dict filter" Tcl command. See the user
+ * documentation for details on what it does, and TIP#111 for the formal
+ * specification.
+ *
+ * Results:
+ * A standard Tcl result.
+ *
+ * Side effects:
+ * See the user documentation.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+DictFilterCmd(
+ ClientData dummy,
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *const *objv)
+{
+ Interp *iPtr = (Interp *) interp;
+ static const char *filters[] = {
+ "key", "script", "value", NULL
+ };
+ enum FilterTypes {
+ FILTER_KEYS, FILTER_SCRIPT, FILTER_VALUES
+ };
+ Tcl_Obj *scriptObj, *keyVarObj, *valueVarObj;
+ Tcl_Obj **varv, *keyObj, *valueObj, *resultObj, *boolObj;
+ Tcl_DictSearch search;
+ int index, varc, done, result, satisfied;
+ char *pattern;
+
+ if (objc < 3) {
+ Tcl_WrongNumArgs(interp, 1, objv, "dictionary filterType ...");
+ return TCL_ERROR;
+ }
+ if (Tcl_GetIndexFromObj(interp, objv[2], filters, "filterType",
+ 0, &index) != TCL_OK) {
+ return TCL_ERROR;
+ }
+
+ switch ((enum FilterTypes) index) {
+ case FILTER_KEYS:
+ if (objc != 4) {
+ Tcl_WrongNumArgs(interp, 1, objv, "dictionary key globPattern");
+ return TCL_ERROR;
+ }
+
+ /*
+ * Create a dictionary whose keys all match a certain pattern.
+ */
+
+ if (Tcl_DictObjFirst(interp, objv[1], &search,
+ &keyObj, &valueObj, &done) != TCL_OK) {
+ return TCL_ERROR;
+ }
+ pattern = TclGetString(objv[3]);
+ resultObj = Tcl_NewDictObj();
+ if (TclMatchIsTrivial(pattern)) {
+ /*
+ * Must release the search lock here to prevent a memory leak
+ * since we are not exhausing the search. [Bug 1705778, leak K05]
+ */
+
+ Tcl_DictObjDone(&search);
+ Tcl_DictObjGet(interp, objv[1], objv[3], &valueObj);
+ if (valueObj != NULL) {
+ Tcl_DictObjPut(interp, resultObj, objv[3], valueObj);
+ }
+ } else {
+ while (!done) {
+ if (Tcl_StringMatch(TclGetString(keyObj), pattern)) {
+ Tcl_DictObjPut(interp, resultObj, keyObj, valueObj);
+ }
+ Tcl_DictObjNext(&search, &keyObj, &valueObj, &done);
+ }
+ }
+ Tcl_SetObjResult(interp, resultObj);
+ return TCL_OK;
+
+ case FILTER_VALUES:
+ if (objc != 4) {
+ Tcl_WrongNumArgs(interp, 1, objv, "dictionary value globPattern");
+ return TCL_ERROR;
+ }
+
+ /*
+ * Create a dictionary whose values all match a certain pattern.
+ */
+
+ if (Tcl_DictObjFirst(interp, objv[1], &search,
+ &keyObj, &valueObj, &done) != TCL_OK) {
+ return TCL_ERROR;
+ }
+ pattern = TclGetString(objv[3]);
+ resultObj = Tcl_NewDictObj();
+ while (!done) {
+ if (Tcl_StringMatch(TclGetString(valueObj), pattern)) {
+ Tcl_DictObjPut(interp, resultObj, keyObj, valueObj);
+ }
+ Tcl_DictObjNext(&search, &keyObj, &valueObj, &done);
+ }
+ Tcl_SetObjResult(interp, resultObj);
+ return TCL_OK;
+
+ case FILTER_SCRIPT:
+ if (objc != 5) {
+ Tcl_WrongNumArgs(interp, 1, objv,
+ "dictionary script {keyVar valueVar} filterScript");
+ return TCL_ERROR;
+ }
+
+ /*
+ * Create a dictionary whose key,value pairs all satisfy a script
+ * (i.e. get a true boolean result from its evaluation). Massive
+ * copying from the "dict for" implementation has occurred!
+ */
+
+ if (TclListObjGetElements(interp, objv[3], &varc, &varv) != TCL_OK) {
+ return TCL_ERROR;
+ }
+ if (varc != 2) {
+ Tcl_SetResult(interp, "must have exactly two variable names",
+ TCL_STATIC);
+ return TCL_ERROR;
+ }
+ keyVarObj = varv[0];
+ valueVarObj = varv[1];
+ scriptObj = objv[4];
+
+ /*
+ * Make sure that these objects (which we need throughout the body of
+ * the loop) don't vanish. Note that the dictionary internal rep is
+ * locked internally so that updates, shimmering, etc are not a
+ * problem.
+ */
+
+ Tcl_IncrRefCount(keyVarObj);
+ Tcl_IncrRefCount(valueVarObj);
+ Tcl_IncrRefCount(scriptObj);
+
+ result = Tcl_DictObjFirst(interp, objv[1],
+ &search, &keyObj, &valueObj, &done);
+ if (result != TCL_OK) {
+ TclDecrRefCount(keyVarObj);
+ TclDecrRefCount(valueVarObj);
+ TclDecrRefCount(scriptObj);
+ return TCL_ERROR;
+ }
+
+ resultObj = Tcl_NewDictObj();
+
+ while (!done) {
+ /*
+ * Stop the value from getting hit in any way by any traces on the
+ * key variable.
+ */
+
+ Tcl_IncrRefCount(keyObj);
+ Tcl_IncrRefCount(valueObj);
+ if (Tcl_ObjSetVar2(interp, keyVarObj, NULL, keyObj,
+ TCL_LEAVE_ERR_MSG) == NULL) {
+ Tcl_ResetResult(interp);
+ Tcl_AppendResult(interp, "couldn't set key variable: \"",
+ TclGetString(keyVarObj), "\"", NULL);
+ result = TCL_ERROR;
+ goto abnormalResult;
+ }
+ if (Tcl_ObjSetVar2(interp, valueVarObj, NULL, valueObj,
+ TCL_LEAVE_ERR_MSG) == NULL) {
+ Tcl_ResetResult(interp);
+ Tcl_AppendResult(interp, "couldn't set value variable: \"",
+ TclGetString(valueVarObj), "\"", NULL);
+ goto abnormalResult;
+ }
+
+ /*
+ * TIP #280. Make invoking context available to loop body.
+ */
+
+ result = TclEvalObjEx(interp, scriptObj, 0, iPtr->cmdFramePtr, 4);
+ switch (result) {
+ case TCL_OK:
+ boolObj = Tcl_GetObjResult(interp);
+ Tcl_IncrRefCount(boolObj);
+ Tcl_ResetResult(interp);
+ if (Tcl_GetBooleanFromObj(interp, boolObj,
+ &satisfied) != TCL_OK) {
+ TclDecrRefCount(boolObj);
+ result = TCL_ERROR;
+ goto abnormalResult;
+ }
+ TclDecrRefCount(boolObj);
+ if (satisfied) {
+ Tcl_DictObjPut(interp, resultObj, keyObj, valueObj);
+ }
+ break;
+ case TCL_BREAK:
+ /*
+ * Force loop termination by calling Tcl_DictObjDone; this
+ * makes the next Tcl_DictObjNext say there is nothing more to
+ * do.
+ */
+
+ Tcl_ResetResult(interp);
+ Tcl_DictObjDone(&search);
+ case TCL_CONTINUE:
+ result = TCL_OK;
+ break;
+ case TCL_ERROR:
+ Tcl_AppendObjToErrorInfo(interp, Tcl_ObjPrintf(
+ "\n (\"dict filter\" script line %d)",
+ interp->errorLine));
+ default:
+ goto abnormalResult;
+ }
+
+ TclDecrRefCount(keyObj);
+ TclDecrRefCount(valueObj);
+
+ Tcl_DictObjNext(&search, &keyObj, &valueObj, &done);
+ }
+
+ /*
+ * Stop holding a reference to these objects.
+ */
+
+ TclDecrRefCount(keyVarObj);
+ TclDecrRefCount(valueVarObj);
+ TclDecrRefCount(scriptObj);
+ Tcl_DictObjDone(&search);
+
+ if (result == TCL_OK) {
+ Tcl_SetObjResult(interp, resultObj);
+ } else {
+ TclDecrRefCount(resultObj);
+ }
+ return result;
+
+ abnormalResult:
+ Tcl_DictObjDone(&search);
+ TclDecrRefCount(keyObj);
+ TclDecrRefCount(valueObj);
+ TclDecrRefCount(keyVarObj);
+ TclDecrRefCount(valueVarObj);
+ TclDecrRefCount(scriptObj);
+ TclDecrRefCount(resultObj);
+ return result;
+ }
+ Tcl_Panic("unexpected fallthrough");
+ /* Control never reaches this point. */
+ return TCL_ERROR;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * DictUpdateCmd --
+ *
+ * This function implements the "dict update" Tcl command. See the user
+ * documentation for details on what it does, and TIP#212 for the formal
+ * specification.
+ *
+ * Results:
+ * A standard Tcl result.
+ *
+ * Side effects:
+ * See the user documentation.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+DictUpdateCmd(
+ ClientData clientData,
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *const *objv)
+{
+ Interp *iPtr = (Interp *) interp;
+ Tcl_Obj *dictPtr, *objPtr;
+ int i, result, dummy;
+ Tcl_InterpState state;
+
+ if (objc < 5 || !(objc & 1)) {
+ Tcl_WrongNumArgs(interp, 1, objv,
+ "varName key varName ?key varName ...? script");
+ return TCL_ERROR;
+ }
+
+ dictPtr = Tcl_ObjGetVar2(interp, objv[1], NULL, TCL_LEAVE_ERR_MSG);
+ if (dictPtr == NULL) {
+ return TCL_ERROR;
+ }
+ if (Tcl_DictObjSize(interp, dictPtr, &dummy) != TCL_OK) {
+ return TCL_ERROR;
+ }
+ Tcl_IncrRefCount(dictPtr);
+ for (i=2 ; i+2<objc ; i+=2) {
+ if (Tcl_DictObjGet(interp, dictPtr, objv[i], &objPtr) != TCL_OK) {
+ TclDecrRefCount(dictPtr);
+ return TCL_ERROR;
+ }
+ if (objPtr == NULL) {
+ /* ??? */
+ Tcl_UnsetVar(interp, Tcl_GetString(objv[i+1]), 0);
+ } else if (Tcl_ObjSetVar2(interp, objv[i+1], NULL, objPtr,
+ TCL_LEAVE_ERR_MSG) == NULL) {
+ TclDecrRefCount(dictPtr);
+ return TCL_ERROR;
+ }
+ }
+ TclDecrRefCount(dictPtr);
+
+ /*
+ * Execute the body.
+ */
+
+ result = TclEvalObjEx(interp, objv[objc-1], 0, iPtr->cmdFramePtr, objc-1);
+ if (result == TCL_ERROR) {
+ Tcl_AddErrorInfo(interp, "\n (body of \"dict update\")");
+ }
+
+ /*
+ * If the dictionary variable doesn't exist, drop everything silently.
+ */
+
+ dictPtr = Tcl_ObjGetVar2(interp, objv[1], NULL, 0);
+ if (dictPtr == NULL) {
+ return result;
+ }
+
+ /*
+ * Double-check that it is still a dictionary.
+ */
+
+ state = Tcl_SaveInterpState(interp, result);
+ if (Tcl_DictObjSize(interp, dictPtr, &dummy) != TCL_OK) {
+ Tcl_DiscardInterpState(state);
+ return TCL_ERROR;
+ }
+
+ if (Tcl_IsShared(dictPtr)) {
+ dictPtr = Tcl_DuplicateObj(dictPtr);
+ }
+
+ /*
+ * Write back the values from the variables, treating failure to read as
+ * an instruction to remove the key.
+ */
+
+ for (i=2 ; i+2<objc ; i+=2) {
+ objPtr = Tcl_ObjGetVar2(interp, objv[i+1], NULL, 0);
+ if (objPtr == NULL) {
+ Tcl_DictObjRemove(interp, dictPtr, objv[i]);
+ } else if (objPtr == dictPtr) {
+ /*
+ * Someone is messing us around, trying to build a recursive
+ * structure. [Bug 1786481]
+ */
+
+ Tcl_DictObjPut(interp, dictPtr, objv[i],
+ Tcl_DuplicateObj(objPtr));
+ } else {
+ /* Shouldn't fail */
+ Tcl_DictObjPut(interp, dictPtr, objv[i], objPtr);
+ }
+ }
+
+ /*
+ * Write the dictionary back to its variable.
+ */
+
+ if (Tcl_ObjSetVar2(interp, objv[1], NULL, dictPtr,
+ TCL_LEAVE_ERR_MSG) == NULL) {
+ Tcl_DiscardInterpState(state);
+ return TCL_ERROR;
+ }
+
+ return Tcl_RestoreInterpState(interp, state);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * DictWithCmd --
+ *
+ * This function implements the "dict with" Tcl command. See the user
+ * documentation for details on what it does, and TIP#212 for the formal
+ * specification.
+ *
+ * Results:
+ * A standard Tcl result.
+ *
+ * Side effects:
+ * See the user documentation.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+DictWithCmd(
+ ClientData dummy,
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *const *objv)
+{
+ Interp *iPtr = (Interp *) interp;
+ Tcl_Obj *dictPtr, *keysPtr, *keyPtr, *valPtr, **keyv, *leafPtr;
+ Tcl_DictSearch s;
+ Tcl_InterpState state;
+ int done, result, keyc, i, allocdict = 0;
+
+ if (objc < 3) {
+ Tcl_WrongNumArgs(interp, 1, objv, "dictVar ?key ...? script");
+ return TCL_ERROR;
+ }
+
+ /*
+ * Get the dictionary to open out.
+ */
+
+ dictPtr = Tcl_ObjGetVar2(interp, objv[1], NULL, TCL_LEAVE_ERR_MSG);
+ if (dictPtr == NULL) {
+ return TCL_ERROR;
+ }
+ if (objc > 3) {
+ dictPtr = TclTraceDictPath(interp, dictPtr, objc-3, objv+2,
+ DICT_PATH_READ);
+ if (dictPtr == NULL) {
+ return TCL_ERROR;
+ }
+ }
+
+ /*
+ * Go over the list of keys and write each corresponding value to a
+ * variable in the current context with the same name. Also keep a copy of
+ * the keys so we can write back properly later on even if the dictionary
+ * has been structurally modified.
+ */
+
+ if (Tcl_DictObjFirst(interp, dictPtr, &s, &keyPtr, &valPtr,
+ &done) != TCL_OK) {
+ return TCL_ERROR;
+ }
+
+ TclNewObj(keysPtr);
+ Tcl_IncrRefCount(keysPtr);
+
+ for (; !done ; Tcl_DictObjNext(&s, &keyPtr, &valPtr, &done)) {
+ Tcl_ListObjAppendElement(NULL, keysPtr, keyPtr);
+ if (Tcl_ObjSetVar2(interp, keyPtr, NULL, valPtr,
+ TCL_LEAVE_ERR_MSG) == NULL) {
+ TclDecrRefCount(keysPtr);
+ Tcl_DictObjDone(&s);
+ return TCL_ERROR;
+ }
+ }
+
+ /*
+ * Execute the body, while making the invoking context available to the
+ * loop body (TIP#280).
+ */
+
+ result = TclEvalObjEx(interp, objv[objc-1], 0, iPtr->cmdFramePtr, objc-1);
+ if (result == TCL_ERROR) {
+ Tcl_AddErrorInfo(interp, "\n (body of \"dict with\")");
+ }
+
+ /*
+ * If the dictionary variable doesn't exist, drop everything silently.
+ */
+
+ dictPtr = Tcl_ObjGetVar2(interp, objv[1], NULL, 0);
+ if (dictPtr == NULL) {
+ TclDecrRefCount(keysPtr);
+ return result;
+ }
+
+ /*
+ * Double-check that it is still a dictionary.
+ */
+
+ state = Tcl_SaveInterpState(interp, result);
+ if (Tcl_DictObjSize(interp, dictPtr, &i) != TCL_OK) {
+ TclDecrRefCount(keysPtr);
+ Tcl_DiscardInterpState(state);
+ return TCL_ERROR;
+ }
+
+ if (Tcl_IsShared(dictPtr)) {
+ dictPtr = Tcl_DuplicateObj(dictPtr);
+ allocdict = 1;
+ }
+
+ if (objc > 3) {
+ /*
+ * Want to get to the dictionary which we will update; need to do
+ * prepare-for-update de-sharing along the path *but* avoid generating
+ * an error on a non-existant path (we'll treat that the same as a
+ * non-existant variable. Luckily, the de-sharing operation isn't
+ * deeply damaging if we don't go on to update; it's just less than
+ * perfectly efficient (but no memory should be leaked).
+ */
+
+ leafPtr = TclTraceDictPath(interp, dictPtr, objc-3, objv+2,
+ DICT_PATH_EXISTS | DICT_PATH_UPDATE);
+ if (leafPtr == NULL) {
+ TclDecrRefCount(keysPtr);
+ if (allocdict) {
+ TclDecrRefCount(dictPtr);
+ }
+ Tcl_DiscardInterpState(state);
+ return TCL_ERROR;
+ }
+ if (leafPtr == DICT_PATH_NON_EXISTENT) {
+ TclDecrRefCount(keysPtr);
+ if (allocdict) {
+ TclDecrRefCount(dictPtr);
+ }
+ return Tcl_RestoreInterpState(interp, state);
+ }
+ } else {
+ leafPtr = dictPtr;
+ }
+
+ /*
+ * Now process our updates on the leaf dictionary.
+ */
+
+ TclListObjGetElements(NULL, keysPtr, &keyc, &keyv);
+ for (i=0 ; i<keyc ; i++) {
+ valPtr = Tcl_ObjGetVar2(interp, keyv[i], NULL, 0);
+ if (valPtr == NULL) {
+ Tcl_DictObjRemove(NULL, leafPtr, keyv[i]);
+ } else if (leafPtr == valPtr) {
+ /*
+ * Someone is messing us around, trying to build a recursive
+ * structure. [Bug 1786481]
+ */
+
+ Tcl_DictObjPut(NULL, leafPtr, keyv[i], Tcl_DuplicateObj(valPtr));
+ } else {
+ Tcl_DictObjPut(NULL, leafPtr, keyv[i], valPtr);
+ }
+ }
+ TclDecrRefCount(keysPtr);
+
+ /*
+ * Ensure that none of the dictionaries in the chain still have a string
+ * rep.
+ */
+
+ if (objc > 3) {
+ InvalidateDictChain(leafPtr);
+ }
+
+ /*
+ * Write back the outermost dictionary to the variable.
+ */
+
+ if (Tcl_ObjSetVar2(interp, objv[1], NULL, dictPtr,
+ TCL_LEAVE_ERR_MSG) == NULL) {
+ Tcl_DiscardInterpState(state);
+ return TCL_ERROR;
+ }
+ return Tcl_RestoreInterpState(interp, state);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TclInitDictCmd --
+ *
+ * This function is create the "dict" Tcl command. See the user
+ * documentation for details on what it does, and TIP#111 for the formal
+ * specification.
+ *
+ * Results:
+ * A Tcl command handle.
+ *
+ * Side effects:
+ * May advance compilation epoch.
+ *
+ *----------------------------------------------------------------------
+ */
+
+Tcl_Command
+TclInitDictCmd(
+ Tcl_Interp *interp)
+{
+ return TclMakeEnsemble(interp, "dict", implementationMap);
+}
+
+/*
+ * Local Variables:
+ * mode: c
+ * c-basic-offset: 4
+ * fill-column: 78
+ * End:
+ */