3662 lines
93 KiB
C
3662 lines
93 KiB
C
/*
|
||
* tclDictObj.c --
|
||
*
|
||
* This file contains functions that implement the Tcl dict object type
|
||
* and its accessor command.
|
||
*
|
||
* Copyright (c) 2002-2010 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 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);
|
||
static Tcl_NRPostProc FinalizeDictUpdate;
|
||
static Tcl_NRPostProc FinalizeDictWith;
|
||
static Tcl_ObjCmdProc DictForNRCmd;
|
||
static Tcl_ObjCmdProc DictMapNRCmd;
|
||
static Tcl_NRPostProc DictForLoopCallback;
|
||
static Tcl_NRPostProc DictMapLoopCallback;
|
||
|
||
/*
|
||
* Table of dict subcommand names and implementations.
|
||
*/
|
||
|
||
static const EnsembleImplMap implementationMap[] = {
|
||
{"append", DictAppendCmd, TclCompileDictAppendCmd, NULL, NULL, 0 },
|
||
{"create", DictCreateCmd, TclCompileDictCreateCmd, NULL, NULL, 0 },
|
||
{"exists", DictExistsCmd, TclCompileDictExistsCmd, NULL, NULL, 0 },
|
||
{"filter", DictFilterCmd, NULL, NULL, NULL, 0 },
|
||
{"for", NULL, TclCompileDictForCmd, DictForNRCmd, NULL, 0 },
|
||
{"get", DictGetCmd, TclCompileDictGetCmd, NULL, NULL, 0 },
|
||
{"incr", DictIncrCmd, TclCompileDictIncrCmd, NULL, NULL, 0 },
|
||
{"info", DictInfoCmd, TclCompileBasic1ArgCmd, NULL, NULL, 0 },
|
||
{"keys", DictKeysCmd, TclCompileBasic1Or2ArgCmd, NULL, NULL, 0 },
|
||
{"lappend", DictLappendCmd, TclCompileDictLappendCmd, NULL, NULL, 0 },
|
||
{"map", NULL, TclCompileDictMapCmd, DictMapNRCmd, NULL, 0 },
|
||
{"merge", DictMergeCmd, TclCompileDictMergeCmd, NULL, NULL, 0 },
|
||
{"remove", DictRemoveCmd, TclCompileBasicMin1ArgCmd, NULL, NULL, 0 },
|
||
{"replace", DictReplaceCmd, NULL, NULL, NULL, 0 },
|
||
{"set", DictSetCmd, TclCompileDictSetCmd, NULL, NULL, 0 },
|
||
{"size", DictSizeCmd, TclCompileBasic1ArgCmd, NULL, NULL, 0 },
|
||
{"unset", DictUnsetCmd, TclCompileDictUnsetCmd, NULL, NULL, 0 },
|
||
{"update", DictUpdateCmd, TclCompileDictUpdateCmd, NULL, NULL, 0 },
|
||
{"values", DictValuesCmd, TclCompileBasic1Or2ArgCmd, NULL, NULL, 0 },
|
||
{"with", DictWithCmd, TclCompileDictWithCmd, NULL, NULL, 0 },
|
||
{NULL, NULL, NULL, NULL, NULL, 0}
|
||
};
|
||
|
||
/*
|
||
* 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 */
|
||
size_t refCount; /* Reference counter (see above) */
|
||
Tcl_Obj *chain; /* Linked list used for invalidating the
|
||
* string representations of updated nested
|
||
* dictionaries. */
|
||
} Dict;
|
||
|
||
/*
|
||
* Accessor macro for converting between a Tcl_Obj* and a Dict. Note that this
|
||
* must be assignable as well as readable.
|
||
*/
|
||
|
||
#define DICT(dictObj) ((dictObj)->internalRep.twoPtrValue.ptr1)
|
||
|
||
/*
|
||
* The structure below defines the dictionary object type by means of
|
||
* functions that can be invoked by generic object code.
|
||
*/
|
||
|
||
const 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 const Tcl_HashKeyType chainHashType = {
|
||
TCL_HASH_KEY_TYPE_VERSION,
|
||
0,
|
||
TclHashObjKey,
|
||
TclCompareObjKeys,
|
||
AllocChainEntry,
|
||
TclFreeObjEntry
|
||
};
|
||
|
||
/*
|
||
* Structure used in implementation of 'dict map' to hold the state that gets
|
||
* passed between parts of the implementation.
|
||
*/
|
||
|
||
typedef struct {
|
||
Tcl_Obj *keyVarObj; /* The name of the variable that will have
|
||
* keys assigned to it. */
|
||
Tcl_Obj *valueVarObj; /* The name of the variable that will have
|
||
* values assigned to it. */
|
||
Tcl_DictSearch search; /* The dictionary search structure. */
|
||
Tcl_Obj *scriptObj; /* The script to evaluate each time through
|
||
* the loop. */
|
||
Tcl_Obj *accumulatorObj; /* The dictionary used to accumulate the
|
||
* results. */
|
||
} DictMapStorage;
|
||
|
||
/***** 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 = ckalloc(sizeof(ChainEntry));
|
||
cPtr->entry.key.objPtr = 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, 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, 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 = DICT(srcPtr);
|
||
Dict *newDict = ckalloc(sizeof(Dict));
|
||
ChainEntry *cPtr;
|
||
|
||
/*
|
||
* Copy values across from the old hash table.
|
||
*/
|
||
|
||
InitChainTable(newDict);
|
||
for (cPtr=oldDict->entryChainHead ; cPtr!=NULL ; cPtr=cPtr->nextPtr) {
|
||
Tcl_Obj *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, valuePtr);
|
||
Tcl_IncrRefCount(valuePtr);
|
||
}
|
||
|
||
/*
|
||
* Initialise other fields.
|
||
*/
|
||
|
||
newDict->epoch = 0;
|
||
newDict->chain = NULL;
|
||
newDict->refCount = 1;
|
||
|
||
/*
|
||
* Store in the object.
|
||
*/
|
||
|
||
DICT(copyPtr) = newDict;
|
||
copyPtr->internalRep.twoPtrValue.ptr2 = NULL;
|
||
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 = DICT(dictPtr);
|
||
|
||
if (dict->refCount-- <= 1) {
|
||
DeleteDict(dict);
|
||
}
|
||
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(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 64
|
||
char localFlags[LOCAL_SIZE], *flagPtr = NULL;
|
||
Dict *dict = DICT(dictPtr);
|
||
ChainEntry *cPtr;
|
||
Tcl_Obj *keyPtr, *valuePtr;
|
||
int i, length, bytesNeeded = 0;
|
||
const char *elem;
|
||
char *dst;
|
||
|
||
/*
|
||
* 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 {
|
||
flagPtr = ckalloc(numElems);
|
||
}
|
||
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_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(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_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(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;
|
||
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;
|
||
|
||
if (TclFindDictElement(interp, nextElem, (limit - nextElem),
|
||
&elemStart, &nextElem, &elemSize, &literal) != TCL_OK) {
|
||
goto errorInFindDictElement;
|
||
}
|
||
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);
|
||
}
|
||
|
||
if (TclFindDictElement(interp, nextElem, (limit - nextElem),
|
||
&elemStart, &nextElem, &elemSize, &literal) != TCL_OK) {
|
||
TclDecrRefCount(keyPtr);
|
||
goto errorInFindDictElement;
|
||
}
|
||
|
||
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;
|
||
DICT(objPtr) = dict;
|
||
objPtr->internalRep.twoPtrValue.ptr2 = NULL;
|
||
objPtr->typePtr = &tclDictType;
|
||
return TCL_OK;
|
||
|
||
missingValue:
|
||
if (interp != NULL) {
|
||
Tcl_SetObjResult(interp, Tcl_NewStringObj(
|
||
"missing value to go with key", -1));
|
||
Tcl_SetErrorCode(interp, "TCL", "VALUE", "DICTIONARY", NULL);
|
||
}
|
||
errorInFindDictElement:
|
||
DeleteChainTable(dict);
|
||
ckfree(dict);
|
||
return TCL_ERROR;
|
||
}
|
||
|
||
/*
|
||
*----------------------------------------------------------------------
|
||
*
|
||
* 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
|
||
&& SetDictFromAny(interp, dictPtr) != TCL_OK) {
|
||
return NULL;
|
||
}
|
||
dict = DICT(dictPtr);
|
||
if (flags & DICT_PATH_UPDATE) {
|
||
dict->chain = NULL;
|
||
}
|
||
|
||
for (i=0 ; i<keyc ; i++) {
|
||
Tcl_HashEntry *hPtr = Tcl_FindHashEntry(&dict->table, 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_SetObjResult(interp, Tcl_ObjPrintf(
|
||
"key \"%s\" not known in dictionary",
|
||
TclGetString(keyv[i])));
|
||
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
|
||
&& SetDictFromAny(interp, tmpObj) != TCL_OK) {
|
||
return NULL;
|
||
}
|
||
}
|
||
|
||
newDict = DICT(tmpObj);
|
||
if (flags & DICT_PATH_UPDATE) {
|
||
if (Tcl_IsShared(tmpObj)) {
|
||
TclDecrRefCount(tmpObj);
|
||
tmpObj = Tcl_DuplicateObj(tmpObj);
|
||
Tcl_IncrRefCount(tmpObj);
|
||
Tcl_SetHashValue(hPtr, tmpObj);
|
||
dict->epoch++;
|
||
newDict = DICT(tmpObj);
|
||
}
|
||
|
||
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 = DICT(dictObj);
|
||
|
||
do {
|
||
TclInvalidateStringRep(dictObj);
|
||
dict->epoch++;
|
||
dictObj = dict->chain;
|
||
if (dictObj == NULL) {
|
||
break;
|
||
}
|
||
dict->chain = NULL;
|
||
dict = DICT(dictObj);
|
||
} 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
|
||
&& SetDictFromAny(interp, dictPtr) != TCL_OK) {
|
||
return TCL_ERROR;
|
||
}
|
||
|
||
if (dictPtr->bytes != NULL) {
|
||
TclInvalidateStringRep(dictPtr);
|
||
}
|
||
dict = DICT(dictPtr);
|
||
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
|
||
&& SetDictFromAny(interp, dictPtr) != TCL_OK) {
|
||
*valuePtrPtr = NULL;
|
||
return TCL_ERROR;
|
||
}
|
||
|
||
dict = DICT(dictPtr);
|
||
hPtr = Tcl_FindHashEntry(&dict->table, 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
|
||
&& SetDictFromAny(interp, dictPtr) != TCL_OK) {
|
||
return TCL_ERROR;
|
||
}
|
||
|
||
dict = DICT(dictPtr);
|
||
if (DeleteChainEntry(dict, keyPtr)) {
|
||
if (dictPtr->bytes != NULL) {
|
||
TclInvalidateStringRep(dictPtr);
|
||
}
|
||
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
|
||
&& SetDictFromAny(interp, dictPtr) != TCL_OK) {
|
||
return TCL_ERROR;
|
||
}
|
||
|
||
dict = DICT(dictPtr);
|
||
*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
|
||
&& SetDictFromAny(interp, dictPtr) != TCL_OK) {
|
||
return TCL_ERROR;
|
||
}
|
||
|
||
dict = DICT(dictPtr);
|
||
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_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_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;
|
||
if (dict->refCount-- <= 1) {
|
||
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 = DICT(dictPtr);
|
||
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 = DICT(dictPtr);
|
||
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);
|
||
TclInvalidateStringRep(dictPtr);
|
||
dict = ckalloc(sizeof(Dict));
|
||
InitChainTable(dict);
|
||
dict->epoch = 0;
|
||
dict->chain = NULL;
|
||
dict->refCount = 1;
|
||
DICT(dictPtr) = dict;
|
||
dictPtr->internalRep.twoPtrValue.ptr2 = NULL;
|
||
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);
|
||
TclInvalidateStringRep(dictPtr);
|
||
dict = ckalloc(sizeof(Dict));
|
||
InitChainTable(dict);
|
||
dict->epoch = 0;
|
||
dict->chain = NULL;
|
||
dict->refCount = 1;
|
||
DICT(dictPtr) = dict;
|
||
dictPtr->internalRep.twoPtrValue.ptr2 = NULL;
|
||
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(NULL, 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 ...?");
|
||
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 = NULL, *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_SetObjResult(interp, Tcl_ObjPrintf(
|
||
"key \"%s\" not known in dictionary",
|
||
TclGetString(objv[objc-1])));
|
||
Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "DICT",
|
||
TclGetString(objv[objc-1]), 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;
|
||
|
||
if ((objc < 2) || (objc & 1)) {
|
||
Tcl_WrongNumArgs(interp, 1, objv, "dictionary ?key value ...?");
|
||
return TCL_ERROR;
|
||
}
|
||
|
||
dictPtr = objv[1];
|
||
if (dictPtr->typePtr != &tclDictType
|
||
&& SetDictFromAny(interp, dictPtr) != TCL_OK) {
|
||
return TCL_ERROR;
|
||
}
|
||
if (Tcl_IsShared(dictPtr)) {
|
||
dictPtr = Tcl_DuplicateObj(dictPtr);
|
||
}
|
||
if (dictPtr->bytes != NULL) {
|
||
TclInvalidateStringRep(dictPtr);
|
||
}
|
||
for (i=2 ; i<objc ; i+=2) {
|
||
Tcl_DictObjPut(NULL, dictPtr, objv[i], objv[i+1]);
|
||
}
|
||
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;
|
||
|
||
if (objc < 2) {
|
||
Tcl_WrongNumArgs(interp, 1, objv, "dictionary ?key ...?");
|
||
return TCL_ERROR;
|
||
}
|
||
|
||
dictPtr = objv[1];
|
||
if (dictPtr->typePtr != &tclDictType
|
||
&& SetDictFromAny(interp, dictPtr) != TCL_OK) {
|
||
return TCL_ERROR;
|
||
}
|
||
if (Tcl_IsShared(dictPtr)) {
|
||
dictPtr = Tcl_DuplicateObj(dictPtr);
|
||
}
|
||
if (dictPtr->bytes != NULL) {
|
||
TclInvalidateStringRep(dictPtr);
|
||
}
|
||
for (i=2 ; i<objc ; i++) {
|
||
Tcl_DictObjRemove(NULL, dictPtr, objv[i]);
|
||
}
|
||
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 = NULL, *valueObj = NULL;
|
||
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
|
||
&& 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;
|
||
const 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
|
||
&& SetDictFromAny(interp, objv[1]) != TCL_OK) {
|
||
return TCL_ERROR;
|
||
}
|
||
|
||
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 = NULL;
|
||
int done = 0;
|
||
|
||
/*
|
||
* 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 = NULL, *listPtr;
|
||
Tcl_DictSearch search;
|
||
int done;
|
||
const 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;
|
||
char *statsStr;
|
||
|
||
if (objc != 2) {
|
||
Tcl_WrongNumArgs(interp, 1, objv, "dictionary");
|
||
return TCL_ERROR;
|
||
}
|
||
|
||
dictPtr = objv[1];
|
||
if (dictPtr->typePtr != &tclDictType
|
||
&& SetDictFromAny(interp, dictPtr) != TCL_OK) {
|
||
return TCL_ERROR;
|
||
}
|
||
dict = DICT(dictPtr);
|
||
|
||
statsStr = Tcl_HashStats(&dict->table);
|
||
Tcl_SetObjResult(interp, Tcl_NewStringObj(statsStr, -1));
|
||
ckfree(statsStr);
|
||
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, "dictVarName 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(NULL, dictPtr, objv[2], objv[3]);
|
||
}
|
||
} else {
|
||
Tcl_DictObjPut(NULL, 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(NULL, dictPtr, objv[2], valuePtr);
|
||
}
|
||
if (objc == 4) {
|
||
code = TclIncrObj(interp, valuePtr, objv[3]);
|
||
} else {
|
||
Tcl_Obj *incrPtr;
|
||
|
||
TclNewIntObj(incrPtr, 1);
|
||
Tcl_IncrRefCount(incrPtr);
|
||
code = TclIncrObj(interp, valuePtr, incrPtr);
|
||
TclDecrRefCount(incrPtr);
|
||
}
|
||
}
|
||
if (code == TCL_OK) {
|
||
TclInvalidateStringRep(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) {
|
||
TclDecrRefCount(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, "dictVarName 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(NULL, dictPtr, objv[2], valuePtr);
|
||
} else if (dictPtr->bytes != NULL) {
|
||
TclInvalidateStringRep(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, "dictVarName 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(NULL, 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;
|
||
}
|
||
|
||
/*
|
||
*----------------------------------------------------------------------
|
||
*
|
||
* DictForNRCmd --
|
||
*
|
||
* These functions implement 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
|
||
DictForNRCmd(
|
||
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 *searchPtr;
|
||
int varc, done;
|
||
|
||
if (objc != 4) {
|
||
Tcl_WrongNumArgs(interp, 1, objv,
|
||
"{keyVarName valueVarName} dictionary script");
|
||
return TCL_ERROR;
|
||
}
|
||
|
||
/*
|
||
* Parse arguments.
|
||
*/
|
||
|
||
if (TclListObjGetElements(interp, objv[1], &varc, &varv) != TCL_OK) {
|
||
return TCL_ERROR;
|
||
}
|
||
if (varc != 2) {
|
||
Tcl_SetObjResult(interp, Tcl_NewStringObj(
|
||
"must have exactly two variable names", -1));
|
||
Tcl_SetErrorCode(interp, "TCL", "SYNTAX", "dict", "for", NULL);
|
||
return TCL_ERROR;
|
||
}
|
||
searchPtr = TclStackAlloc(interp, sizeof(Tcl_DictSearch));
|
||
if (Tcl_DictObjFirst(interp, objv[2], searchPtr, &keyObj, &valueObj,
|
||
&done) != TCL_OK) {
|
||
TclStackFree(interp, searchPtr);
|
||
return TCL_ERROR;
|
||
}
|
||
if (done) {
|
||
TclStackFree(interp, searchPtr);
|
||
return TCL_OK;
|
||
}
|
||
TclListObjGetElements(NULL, objv[1], &varc, &varv);
|
||
keyVarObj = varv[0];
|
||
valueVarObj = varv[1];
|
||
scriptObj = objv[3];
|
||
|
||
/*
|
||
* 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);
|
||
|
||
/*
|
||
* 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,
|
||
TCL_LEAVE_ERR_MSG) == NULL) {
|
||
TclDecrRefCount(valueObj);
|
||
goto error;
|
||
}
|
||
TclDecrRefCount(valueObj);
|
||
if (Tcl_ObjSetVar2(interp, valueVarObj, NULL, valueObj,
|
||
TCL_LEAVE_ERR_MSG) == NULL) {
|
||
goto error;
|
||
}
|
||
|
||
/*
|
||
* Run the script.
|
||
*/
|
||
|
||
TclNRAddCallback(interp, DictForLoopCallback, searchPtr, keyVarObj,
|
||
valueVarObj, scriptObj);
|
||
return TclNREvalObjEx(interp, scriptObj, 0, iPtr->cmdFramePtr, 3);
|
||
|
||
/*
|
||
* For unwinding everything on error.
|
||
*/
|
||
|
||
error:
|
||
TclDecrRefCount(keyVarObj);
|
||
TclDecrRefCount(valueVarObj);
|
||
TclDecrRefCount(scriptObj);
|
||
Tcl_DictObjDone(searchPtr);
|
||
TclStackFree(interp, searchPtr);
|
||
return TCL_ERROR;
|
||
}
|
||
|
||
static int
|
||
DictForLoopCallback(
|
||
ClientData data[],
|
||
Tcl_Interp *interp,
|
||
int result)
|
||
{
|
||
Interp *iPtr = (Interp *) interp;
|
||
Tcl_DictSearch *searchPtr = data[0];
|
||
Tcl_Obj *keyVarObj = data[1];
|
||
Tcl_Obj *valueVarObj = data[2];
|
||
Tcl_Obj *scriptObj = data[3];
|
||
Tcl_Obj *keyObj, *valueObj;
|
||
int done;
|
||
|
||
/*
|
||
* Process the result from the previous execution of the script body.
|
||
*/
|
||
|
||
if (result == TCL_CONTINUE) {
|
||
result = TCL_OK;
|
||
} else if (result != TCL_OK) {
|
||
if (result == TCL_BREAK) {
|
||
Tcl_ResetResult(interp);
|
||
result = TCL_OK;
|
||
} else if (result == TCL_ERROR) {
|
||
Tcl_AppendObjToErrorInfo(interp, Tcl_ObjPrintf(
|
||
"\n (\"dict for\" body line %d)",
|
||
Tcl_GetErrorLine(interp)));
|
||
}
|
||
goto done;
|
||
}
|
||
|
||
/*
|
||
* Get the next mapping from the dictionary.
|
||
*/
|
||
|
||
Tcl_DictObjNext(searchPtr, &keyObj, &valueObj, &done);
|
||
if (done) {
|
||
Tcl_ResetResult(interp);
|
||
goto 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,
|
||
TCL_LEAVE_ERR_MSG) == NULL) {
|
||
TclDecrRefCount(valueObj);
|
||
result = TCL_ERROR;
|
||
goto done;
|
||
}
|
||
TclDecrRefCount(valueObj);
|
||
if (Tcl_ObjSetVar2(interp, valueVarObj, NULL, valueObj,
|
||
TCL_LEAVE_ERR_MSG) == NULL) {
|
||
result = TCL_ERROR;
|
||
goto done;
|
||
}
|
||
|
||
/*
|
||
* Run the script.
|
||
*/
|
||
|
||
TclNRAddCallback(interp, DictForLoopCallback, searchPtr, keyVarObj,
|
||
valueVarObj, scriptObj);
|
||
return TclNREvalObjEx(interp, scriptObj, 0, iPtr->cmdFramePtr, 3);
|
||
|
||
/*
|
||
* For unwinding everything once the iterating is done.
|
||
*/
|
||
|
||
done:
|
||
TclDecrRefCount(keyVarObj);
|
||
TclDecrRefCount(valueVarObj);
|
||
TclDecrRefCount(scriptObj);
|
||
Tcl_DictObjDone(searchPtr);
|
||
TclStackFree(interp, searchPtr);
|
||
return result;
|
||
}
|
||
|
||
/*
|
||
*----------------------------------------------------------------------
|
||
*
|
||
* DictMapNRCmd --
|
||
*
|
||
* These functions implement the "dict map" Tcl command. See the user
|
||
* documentation for details on what it does, and TIP#405 for the formal
|
||
* specification.
|
||
*
|
||
* Results:
|
||
* A standard Tcl result.
|
||
*
|
||
* Side effects:
|
||
* See the user documentation.
|
||
*
|
||
*----------------------------------------------------------------------
|
||
*/
|
||
|
||
static int
|
||
DictMapNRCmd(
|
||
ClientData dummy,
|
||
Tcl_Interp *interp,
|
||
int objc,
|
||
Tcl_Obj *const *objv)
|
||
{
|
||
Interp *iPtr = (Interp *) interp;
|
||
Tcl_Obj **varv, *keyObj, *valueObj;
|
||
DictMapStorage *storagePtr;
|
||
int varc, done;
|
||
|
||
if (objc != 4) {
|
||
Tcl_WrongNumArgs(interp, 1, objv,
|
||
"{keyVarName valueVarName} dictionary script");
|
||
return TCL_ERROR;
|
||
}
|
||
|
||
/*
|
||
* Parse arguments.
|
||
*/
|
||
|
||
if (TclListObjGetElements(interp, objv[1], &varc, &varv) != TCL_OK) {
|
||
return TCL_ERROR;
|
||
}
|
||
if (varc != 2) {
|
||
Tcl_SetObjResult(interp, Tcl_NewStringObj(
|
||
"must have exactly two variable names", -1));
|
||
Tcl_SetErrorCode(interp, "TCL", "SYNTAX", "dict", "map", NULL);
|
||
return TCL_ERROR;
|
||
}
|
||
storagePtr = TclStackAlloc(interp, sizeof(DictMapStorage));
|
||
if (Tcl_DictObjFirst(interp, objv[2], &storagePtr->search, &keyObj,
|
||
&valueObj, &done) != TCL_OK) {
|
||
TclStackFree(interp, storagePtr);
|
||
return TCL_ERROR;
|
||
}
|
||
if (done) {
|
||
/*
|
||
* Note that this exit leaves an empty value in the result (due to
|
||
* command calling conventions) but that is OK since an empty value is
|
||
* an empty dictionary.
|
||
*/
|
||
|
||
TclStackFree(interp, storagePtr);
|
||
return TCL_OK;
|
||
}
|
||
TclNewObj(storagePtr->accumulatorObj);
|
||
TclListObjGetElements(NULL, objv[1], &varc, &varv);
|
||
storagePtr->keyVarObj = varv[0];
|
||
storagePtr->valueVarObj = varv[1];
|
||
storagePtr->scriptObj = objv[3];
|
||
|
||
/*
|
||
* 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(storagePtr->accumulatorObj);
|
||
Tcl_IncrRefCount(storagePtr->keyVarObj);
|
||
Tcl_IncrRefCount(storagePtr->valueVarObj);
|
||
Tcl_IncrRefCount(storagePtr->scriptObj);
|
||
|
||
/*
|
||
* Stop the value from getting hit in any way by any traces on the key
|
||
* variable.
|
||
*/
|
||
|
||
Tcl_IncrRefCount(valueObj);
|
||
if (Tcl_ObjSetVar2(interp, storagePtr->keyVarObj, NULL, keyObj,
|
||
TCL_LEAVE_ERR_MSG) == NULL) {
|
||
TclDecrRefCount(valueObj);
|
||
goto error;
|
||
}
|
||
if (Tcl_ObjSetVar2(interp, storagePtr->valueVarObj, NULL, valueObj,
|
||
TCL_LEAVE_ERR_MSG) == NULL) {
|
||
TclDecrRefCount(valueObj);
|
||
goto error;
|
||
}
|
||
TclDecrRefCount(valueObj);
|
||
|
||
/*
|
||
* Run the script.
|
||
*/
|
||
|
||
TclNRAddCallback(interp, DictMapLoopCallback, storagePtr, NULL,NULL,NULL);
|
||
return TclNREvalObjEx(interp, storagePtr->scriptObj, 0,
|
||
iPtr->cmdFramePtr, 3);
|
||
|
||
/*
|
||
* For unwinding everything on error.
|
||
*/
|
||
|
||
error:
|
||
TclDecrRefCount(storagePtr->keyVarObj);
|
||
TclDecrRefCount(storagePtr->valueVarObj);
|
||
TclDecrRefCount(storagePtr->scriptObj);
|
||
TclDecrRefCount(storagePtr->accumulatorObj);
|
||
Tcl_DictObjDone(&storagePtr->search);
|
||
TclStackFree(interp, storagePtr);
|
||
return TCL_ERROR;
|
||
}
|
||
|
||
static int
|
||
DictMapLoopCallback(
|
||
ClientData data[],
|
||
Tcl_Interp *interp,
|
||
int result)
|
||
{
|
||
Interp *iPtr = (Interp *) interp;
|
||
DictMapStorage *storagePtr = data[0];
|
||
Tcl_Obj *keyObj, *valueObj;
|
||
int done;
|
||
|
||
/*
|
||
* Process the result from the previous execution of the script body.
|
||
*/
|
||
|
||
if (result == TCL_CONTINUE) {
|
||
result = TCL_OK;
|
||
} else if (result != TCL_OK) {
|
||
if (result == TCL_BREAK) {
|
||
Tcl_ResetResult(interp);
|
||
result = TCL_OK;
|
||
} else if (result == TCL_ERROR) {
|
||
Tcl_AppendObjToErrorInfo(interp, Tcl_ObjPrintf(
|
||
"\n (\"dict map\" body line %d)",
|
||
Tcl_GetErrorLine(interp)));
|
||
}
|
||
goto done;
|
||
} else {
|
||
keyObj = Tcl_ObjGetVar2(interp, storagePtr->keyVarObj, NULL,
|
||
TCL_LEAVE_ERR_MSG);
|
||
if (keyObj == NULL) {
|
||
result = TCL_ERROR;
|
||
goto done;
|
||
}
|
||
Tcl_DictObjPut(NULL, storagePtr->accumulatorObj, keyObj,
|
||
Tcl_GetObjResult(interp));
|
||
}
|
||
|
||
/*
|
||
* Get the next mapping from the dictionary.
|
||
*/
|
||
|
||
Tcl_DictObjNext(&storagePtr->search, &keyObj, &valueObj, &done);
|
||
if (done) {
|
||
Tcl_SetObjResult(interp, storagePtr->accumulatorObj);
|
||
goto done;
|
||
}
|
||
|
||
/*
|
||
* Stop the value from getting hit in any way by any traces on the key
|
||
* variable.
|
||
*/
|
||
|
||
Tcl_IncrRefCount(valueObj);
|
||
if (Tcl_ObjSetVar2(interp, storagePtr->keyVarObj, NULL, keyObj,
|
||
TCL_LEAVE_ERR_MSG) == NULL) {
|
||
TclDecrRefCount(valueObj);
|
||
result = TCL_ERROR;
|
||
goto done;
|
||
}
|
||
if (Tcl_ObjSetVar2(interp, storagePtr->valueVarObj, NULL, valueObj,
|
||
TCL_LEAVE_ERR_MSG) == NULL) {
|
||
TclDecrRefCount(valueObj);
|
||
result = TCL_ERROR;
|
||
goto done;
|
||
}
|
||
TclDecrRefCount(valueObj);
|
||
|
||
/*
|
||
* Run the script.
|
||
*/
|
||
|
||
TclNRAddCallback(interp, DictMapLoopCallback, storagePtr, NULL,NULL,NULL);
|
||
return TclNREvalObjEx(interp, storagePtr->scriptObj, 0,
|
||
iPtr->cmdFramePtr, 3);
|
||
|
||
/*
|
||
* For unwinding everything once the iterating is done.
|
||
*/
|
||
|
||
done:
|
||
TclDecrRefCount(storagePtr->keyVarObj);
|
||
TclDecrRefCount(storagePtr->valueVarObj);
|
||
TclDecrRefCount(storagePtr->scriptObj);
|
||
TclDecrRefCount(storagePtr->accumulatorObj);
|
||
Tcl_DictObjDone(&storagePtr->search);
|
||
TclStackFree(interp, storagePtr);
|
||
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, "dictVarName 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, "dictVarName 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 *const filters[] = {
|
||
"key", "script", "value", NULL
|
||
};
|
||
enum FilterTypes {
|
||
FILTER_KEYS, FILTER_SCRIPT, FILTER_VALUES
|
||
};
|
||
Tcl_Obj *scriptObj, *keyVarObj, *valueVarObj;
|
||
Tcl_Obj **varv, *keyObj = NULL, *valueObj = NULL, *resultObj, *boolObj;
|
||
Tcl_DictSearch search;
|
||
int index, varc, done, result, satisfied;
|
||
const char *pattern;
|
||
|
||
if (objc < 3) {
|
||
Tcl_WrongNumArgs(interp, 1, objv, "dictionary filterType ?arg ...?");
|
||
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:
|
||
/*
|
||
* 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;
|
||
}
|
||
if (objc == 3) {
|
||
/*
|
||
* Nothing to match, so return nothing (== empty dictionary).
|
||
*/
|
||
|
||
Tcl_DictObjDone(&search);
|
||
return TCL_OK;
|
||
} else if (objc == 4) {
|
||
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(NULL, resultObj, objv[3], valueObj);
|
||
}
|
||
} else {
|
||
while (!done) {
|
||
if (Tcl_StringMatch(TclGetString(keyObj), pattern)) {
|
||
Tcl_DictObjPut(NULL, resultObj, keyObj, valueObj);
|
||
}
|
||
Tcl_DictObjNext(&search, &keyObj, &valueObj, &done);
|
||
}
|
||
}
|
||
} else {
|
||
/*
|
||
* Can't optimize this match for trivial globbing: would disturb
|
||
* order.
|
||
*/
|
||
|
||
resultObj = Tcl_NewDictObj();
|
||
while (!done) {
|
||
int i;
|
||
|
||
for (i=3 ; i<objc ; i++) {
|
||
pattern = TclGetString(objv[i]);
|
||
if (Tcl_StringMatch(TclGetString(keyObj), pattern)) {
|
||
Tcl_DictObjPut(NULL, resultObj, keyObj, valueObj);
|
||
break; /* stop inner loop */
|
||
}
|
||
}
|
||
Tcl_DictObjNext(&search, &keyObj, &valueObj, &done);
|
||
}
|
||
}
|
||
Tcl_SetObjResult(interp, resultObj);
|
||
return TCL_OK;
|
||
|
||
case FILTER_VALUES:
|
||
/*
|
||
* 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;
|
||
}
|
||
resultObj = Tcl_NewDictObj();
|
||
while (!done) {
|
||
int i;
|
||
|
||
for (i=3 ; i<objc ; i++) {
|
||
pattern = TclGetString(objv[i]);
|
||
if (Tcl_StringMatch(TclGetString(valueObj), pattern)) {
|
||
Tcl_DictObjPut(NULL, resultObj, keyObj, valueObj);
|
||
break; /* stop inner loop */
|
||
}
|
||
}
|
||
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 {keyVarName valueVarName} 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_SetObjResult(interp, Tcl_NewStringObj(
|
||
"must have exactly two variable names", -1));
|
||
Tcl_SetErrorCode(interp, "TCL", "SYNTAX", "dict", "filter", NULL);
|
||
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_AddErrorInfo(interp,
|
||
"\n (\"dict filter\" filter script key variable)");
|
||
result = TCL_ERROR;
|
||
goto abnormalResult;
|
||
}
|
||
if (Tcl_ObjSetVar2(interp, valueVarObj, NULL, valueObj,
|
||
TCL_LEAVE_ERR_MSG) == NULL) {
|
||
Tcl_AddErrorInfo(interp,
|
||
"\n (\"dict filter\" filter script value variable)");
|
||
result = TCL_ERROR;
|
||
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(NULL, 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);
|
||
/* FALLTHRU */
|
||
case TCL_CONTINUE:
|
||
result = TCL_OK;
|
||
break;
|
||
case TCL_ERROR:
|
||
Tcl_AppendObjToErrorInfo(interp, Tcl_ObjPrintf(
|
||
"\n (\"dict filter\" script line %d)",
|
||
Tcl_GetErrorLine(interp)));
|
||
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, dummy;
|
||
|
||
if (objc < 5 || !(objc & 1)) {
|
||
Tcl_WrongNumArgs(interp, 1, objv,
|
||
"dictVarName 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 after setting up the NRE handler to process the
|
||
* results.
|
||
*/
|
||
|
||
objPtr = Tcl_NewListObj(objc-3, objv+2);
|
||
Tcl_IncrRefCount(objPtr);
|
||
Tcl_IncrRefCount(objv[1]);
|
||
TclNRAddCallback(interp, FinalizeDictUpdate, objv[1], objPtr, NULL,NULL);
|
||
|
||
return TclNREvalObjEx(interp, objv[objc-1], 0, iPtr->cmdFramePtr, objc-1);
|
||
}
|
||
|
||
static int
|
||
FinalizeDictUpdate(
|
||
ClientData data[],
|
||
Tcl_Interp *interp,
|
||
int result)
|
||
{
|
||
Tcl_Obj *dictPtr, *objPtr, **objv;
|
||
Tcl_InterpState state;
|
||
int i, objc;
|
||
Tcl_Obj *varName = data[0];
|
||
Tcl_Obj *argsObj = data[1];
|
||
|
||
/*
|
||
* ErrorInfo handling.
|
||
*/
|
||
|
||
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, varName, NULL, 0);
|
||
if (dictPtr == NULL) {
|
||
TclDecrRefCount(varName);
|
||
TclDecrRefCount(argsObj);
|
||
return result;
|
||
}
|
||
|
||
/*
|
||
* Double-check that it is still a dictionary.
|
||
*/
|
||
|
||
state = Tcl_SaveInterpState(interp, result);
|
||
if (Tcl_DictObjSize(interp, dictPtr, &objc) != TCL_OK) {
|
||
Tcl_DiscardInterpState(state);
|
||
TclDecrRefCount(varName);
|
||
TclDecrRefCount(argsObj);
|
||
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.
|
||
*/
|
||
|
||
Tcl_ListObjGetElements(NULL, argsObj, &objc, &objv);
|
||
for (i=0 ; i<objc ; i+=2) {
|
||
objPtr = Tcl_ObjGetVar2(interp, objv[i+1], NULL, 0);
|
||
if (objPtr == NULL) {
|
||
Tcl_DictObjRemove(NULL, dictPtr, objv[i]);
|
||
} else if (objPtr == dictPtr) {
|
||
/*
|
||
* Someone is messing us around, trying to build a recursive
|
||
* structure. [Bug 1786481]
|
||
*/
|
||
|
||
Tcl_DictObjPut(NULL, dictPtr, objv[i], Tcl_DuplicateObj(objPtr));
|
||
} else {
|
||
/* Shouldn't fail */
|
||
Tcl_DictObjPut(NULL, dictPtr, objv[i], objPtr);
|
||
}
|
||
}
|
||
TclDecrRefCount(argsObj);
|
||
|
||
/*
|
||
* Write the dictionary back to its variable.
|
||
*/
|
||
|
||
if (Tcl_ObjSetVar2(interp, varName, NULL, dictPtr,
|
||
TCL_LEAVE_ERR_MSG) == NULL) {
|
||
Tcl_DiscardInterpState(state);
|
||
TclDecrRefCount(varName);
|
||
return TCL_ERROR;
|
||
}
|
||
|
||
TclDecrRefCount(varName);
|
||
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, *pathPtr;
|
||
|
||
if (objc < 3) {
|
||
Tcl_WrongNumArgs(interp, 1, objv, "dictVarName ?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;
|
||
}
|
||
|
||
keysPtr = TclDictWithInit(interp, dictPtr, objc-3, objv+2);
|
||
if (keysPtr == NULL) {
|
||
return TCL_ERROR;
|
||
}
|
||
Tcl_IncrRefCount(keysPtr);
|
||
|
||
/*
|
||
* Execute the body, while making the invoking context available to the
|
||
* loop body (TIP#280) and postponing the cleanup until later (NRE).
|
||
*/
|
||
|
||
pathPtr = NULL;
|
||
if (objc > 3) {
|
||
pathPtr = Tcl_NewListObj(objc-3, objv+2);
|
||
Tcl_IncrRefCount(pathPtr);
|
||
}
|
||
Tcl_IncrRefCount(objv[1]);
|
||
TclNRAddCallback(interp, FinalizeDictWith, objv[1], keysPtr, pathPtr,
|
||
NULL);
|
||
|
||
return TclNREvalObjEx(interp, objv[objc-1], 0, iPtr->cmdFramePtr, objc-1);
|
||
}
|
||
|
||
static int
|
||
FinalizeDictWith(
|
||
ClientData data[],
|
||
Tcl_Interp *interp,
|
||
int result)
|
||
{
|
||
Tcl_Obj **pathv;
|
||
int pathc;
|
||
Tcl_InterpState state;
|
||
Tcl_Obj *varName = data[0];
|
||
Tcl_Obj *keysPtr = data[1];
|
||
Tcl_Obj *pathPtr = data[2];
|
||
Var *varPtr, *arrayPtr;
|
||
|
||
if (result == TCL_ERROR) {
|
||
Tcl_AddErrorInfo(interp, "\n (body of \"dict with\")");
|
||
}
|
||
|
||
/*
|
||
* Save the result state; TDWF doesn't guarantee to not modify that on
|
||
* TCL_OK result.
|
||
*/
|
||
|
||
state = Tcl_SaveInterpState(interp, result);
|
||
if (pathPtr != NULL) {
|
||
Tcl_ListObjGetElements(NULL, pathPtr, &pathc, &pathv);
|
||
} else {
|
||
pathc = 0;
|
||
pathv = NULL;
|
||
}
|
||
|
||
/*
|
||
* Pack from local variables back into the dictionary.
|
||
*/
|
||
|
||
varPtr = TclObjLookupVarEx(interp, varName, NULL, TCL_LEAVE_ERR_MSG, "set",
|
||
/*createPart1*/ 1, /*createPart2*/ 1, &arrayPtr);
|
||
if (varPtr == NULL) {
|
||
result = TCL_ERROR;
|
||
} else {
|
||
result = TclDictWithFinish(interp, varPtr, arrayPtr, varName, NULL, -1,
|
||
pathc, pathv, keysPtr);
|
||
}
|
||
|
||
/*
|
||
* Tidy up and return the real result (unless we had an error).
|
||
*/
|
||
|
||
TclDecrRefCount(varName);
|
||
TclDecrRefCount(keysPtr);
|
||
if (pathPtr != NULL) {
|
||
TclDecrRefCount(pathPtr);
|
||
}
|
||
if (result != TCL_OK) {
|
||
Tcl_DiscardInterpState(state);
|
||
return TCL_ERROR;
|
||
}
|
||
return Tcl_RestoreInterpState(interp, state);
|
||
}
|
||
|
||
/*
|
||
*----------------------------------------------------------------------
|
||
*
|
||
* TclDictWithInit --
|
||
*
|
||
* Part of the core of [dict with]. Pokes into a dictionary and converts
|
||
* the mappings there into assignments to (presumably) local variables.
|
||
* Returns a list of all the names that were mapped so that removal of
|
||
* either the variable or the dictionary entry won't surprise us when we
|
||
* come to stuffing everything back.
|
||
*
|
||
* Result:
|
||
* List of mapped names, or NULL if there was an error.
|
||
*
|
||
* Side effects:
|
||
* Assigns to variables, so potentially legion due to traces.
|
||
*
|
||
*----------------------------------------------------------------------
|
||
*/
|
||
|
||
Tcl_Obj *
|
||
TclDictWithInit(
|
||
Tcl_Interp *interp,
|
||
Tcl_Obj *dictPtr,
|
||
int pathc,
|
||
Tcl_Obj *const pathv[])
|
||
{
|
||
Tcl_DictSearch s;
|
||
Tcl_Obj *keyPtr, *valPtr, *keysPtr;
|
||
int done;
|
||
|
||
if (pathc > 0) {
|
||
dictPtr = TclTraceDictPath(interp, dictPtr, pathc, pathv,
|
||
DICT_PATH_READ);
|
||
if (dictPtr == NULL) {
|
||
return NULL;
|
||
}
|
||
}
|
||
|
||
/*
|
||
* 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 NULL;
|
||
}
|
||
|
||
TclNewObj(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 NULL;
|
||
}
|
||
}
|
||
|
||
return keysPtr;
|
||
}
|
||
|
||
/*
|
||
*----------------------------------------------------------------------
|
||
*
|
||
* TclDictWithFinish --
|
||
*
|
||
* Part of the core of [dict with]. Reassembles the piece of the dict (in
|
||
* varName, location given by pathc/pathv) from the variables named in
|
||
* the keysPtr argument. NB, does not try to preserve errors or manage
|
||
* argument lifetimes.
|
||
*
|
||
* Result:
|
||
* TCL_OK if we succeeded, or TCL_ERROR if we failed.
|
||
*
|
||
* Side effects:
|
||
* Assigns to a variable, so potentially legion due to traces. Updates
|
||
* the dictionary in the named variable.
|
||
*
|
||
*----------------------------------------------------------------------
|
||
*/
|
||
|
||
int
|
||
TclDictWithFinish(
|
||
Tcl_Interp *interp, /* Command interpreter in which variable
|
||
* exists. Used for state management, traces
|
||
* and error reporting. */
|
||
Var *varPtr, /* Reference to the variable holding the
|
||
* dictionary. */
|
||
Var *arrayPtr, /* Reference to the array containing the
|
||
* variable, or NULL if the variable is a
|
||
* scalar. */
|
||
Tcl_Obj *part1Ptr, /* Name of an array (if part2 is non-NULL) or
|
||
* the name of a variable. NULL if the 'index'
|
||
* parameter is >= 0 */
|
||
Tcl_Obj *part2Ptr, /* If non-NULL, gives the name of an element
|
||
* in the array part1. */
|
||
int index, /* Index into the local variable table of the
|
||
* variable, or -1. Only used when part1Ptr is
|
||
* NULL. */
|
||
int pathc, /* The number of elements in the path into the
|
||
* dictionary. */
|
||
Tcl_Obj *const pathv[], /* The elements of the path to the subdict. */
|
||
Tcl_Obj *keysPtr) /* List of keys to be synchronized. This is
|
||
* the result value from TclDictWithInit. */
|
||
{
|
||
Tcl_Obj *dictPtr, *leafPtr, *valPtr;
|
||
int i, allocdict, keyc;
|
||
Tcl_Obj **keyv;
|
||
|
||
/*
|
||
* If the dictionary variable doesn't exist, drop everything silently.
|
||
*/
|
||
|
||
dictPtr = TclPtrGetVarIdx(interp, varPtr, arrayPtr, part1Ptr, part2Ptr,
|
||
TCL_LEAVE_ERR_MSG, index);
|
||
if (dictPtr == NULL) {
|
||
return TCL_OK;
|
||
}
|
||
|
||
/*
|
||
* Double-check that it is still a dictionary.
|
||
*/
|
||
|
||
if (Tcl_DictObjSize(interp, dictPtr, &i) != TCL_OK) {
|
||
return TCL_ERROR;
|
||
}
|
||
|
||
if (Tcl_IsShared(dictPtr)) {
|
||
dictPtr = Tcl_DuplicateObj(dictPtr);
|
||
allocdict = 1;
|
||
} else {
|
||
allocdict = 0;
|
||
}
|
||
|
||
if (pathc > 0) {
|
||
/*
|
||
* 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, pathc, pathv,
|
||
DICT_PATH_EXISTS | DICT_PATH_UPDATE);
|
||
if (leafPtr == NULL) {
|
||
if (allocdict) {
|
||
TclDecrRefCount(dictPtr);
|
||
}
|
||
return TCL_ERROR;
|
||
}
|
||
if (leafPtr == DICT_PATH_NON_EXISTENT) {
|
||
if (allocdict) {
|
||
TclDecrRefCount(dictPtr);
|
||
}
|
||
return TCL_OK;
|
||
}
|
||
} 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);
|
||
}
|
||
}
|
||
|
||
/*
|
||
* Ensure that none of the dictionaries in the chain still have a string
|
||
* rep.
|
||
*/
|
||
|
||
if (pathc > 0) {
|
||
InvalidateDictChain(leafPtr);
|
||
}
|
||
|
||
/*
|
||
* Write back the outermost dictionary to the variable.
|
||
*/
|
||
|
||
if (TclPtrSetVarIdx(interp, varPtr, arrayPtr, part1Ptr, part2Ptr,
|
||
dictPtr, TCL_LEAVE_ERR_MSG, index) == NULL) {
|
||
if (allocdict) {
|
||
TclDecrRefCount(dictPtr);
|
||
}
|
||
return TCL_ERROR;
|
||
}
|
||
return TCL_OK;
|
||
}
|
||
|
||
/*
|
||
*----------------------------------------------------------------------
|
||
*
|
||
* 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:
|
||
*/
|