1 /*
2  * tclXkeylist.c --
3  *
4  *  Extended Tcl keyed list commands and interfaces.
5  *-----------------------------------------------------------------------------
6  * Copyright 1991-1999 Karl Lehenbauer and Mark Diekhans.
7  *
8  * Permission to use, copy, modify, and distribute this software and its
9  * documentation for any purpose and without fee is hereby granted, provided
10  * that the above copyright notice appear in all copies.  Karl Lehenbauer and
11  * Mark Diekhans make no representations about the suitability of this
12  * software for any purpose.  It is provided "as is" without express or
13  * implied warranty.
14  *-----------------------------------------------------------------------------
15  * $Id: tclXkeylist.c,v 1.8 2005/11/21 18:54:13 hobbs Exp $
16  *-----------------------------------------------------------------------------
17  */
18 
19 #include "tclExtdInt.h"
20 
21 /*
22  * Keyed lists are stored as arrays recursively defined objects.  The data
23  * portion of a keyed list entry is a Tcl_Obj which may be a keyed list object
24  * or any other Tcl object.  Since determine the structure of a keyed list is
25  * lazy (you don't know if an element is data or another keyed list) until it
26  * is accessed, the object can be transformed into a keyed list from a Tcl
27  * string or list.
28  */
29 
30 /*
31  * Adding a hash table over the entries allows for much faster Find
32  * access to the keys (hash lookup instead of list search).  This adds
33  * a hash table to each keyed list object.  That uses more memory, but
34  * you can get an order of magnitude better performance with large
35  * keyed list sets.  Uncomment this line to not use the hash table.
36  */
37 /* #define NO_KEYLIST_HASH_TABLE */
38 
39 /*
40  * An entry in a keyed list array.
41  *
42  * JH: There was the supposition that making the key an object would
43  * be faster, but I tried that and didn't find it to be true.  The
44  * use of the layered hash table is a big win though.
45  */
46 typedef struct {
47     char *key;
48     int keyLen;
49     Tcl_Obj *valuePtr;
50 } keylEntry_t;
51 
52 /*
53  * Internal representation of a keyed list object.
54  */
55 typedef struct {
56     int		 arraySize;   /* Current slots available in the array.	*/
57     int		 numEntries;  /* Number of actual entries in the array. */
58     keylEntry_t *entries;     /* Array of keyed list entries.		*/
59 #ifndef NO_KEYLIST_HASH_TABLE
60     Tcl_HashTable *hashTbl;   /* hash table mirror of the entries */
61                               /* to improve speed */
62 #endif
63 } keylIntObj_t;
64 
65 /*
66  * Amount to increment array size by when it needs to grow.
67  */
68 #define KEYEDLIST_ARRAY_INCR_SIZE 16
69 
70 /*
71  * Macro to duplicate a child entry of a keyed list if it is share by more
72  * than the parent.
73  * NO_KEYLIST_HASH_TABLE: We don't duplicate the hash table, so ensure
74  * that consistency checks allow for portions where not all entries are
75  * in the hash table.
76  */
77 #define DupSharedKeyListChild(keylIntPtr, idx) \
78     if (Tcl_IsShared(keylIntPtr->entries [idx].valuePtr)) { \
79 	keylIntPtr->entries [idx].valuePtr = \
80 	    Tcl_DuplicateObj (keylIntPtr->entries [idx].valuePtr); \
81 	Tcl_IncrRefCount(keylIntPtr->entries [idx].valuePtr); \
82     }
83 
84 /*
85  * Macros to validate an keyed list object or internal representation
86  */
87 #ifdef TCLX_DEBUG
88 #   define KEYL_OBJ_ASSERT(keylAPtr) {\
89 	TclX_Assert (keylAPtr->typePtr == &keyedListType); \
90 	ValidateKeyedList (keylAIntPtr); \
91     }
92 #   define KEYL_REP_ASSERT(keylAIntPtr) \
93 	ValidateKeyedList (keylAIntPtr)
94 #else
95 #  define KEYL_REP_ASSERT(keylAIntPtr)
96 #endif
97 
98 
99 /*
100  * Prototypes of internal functions.
101  */
102 #ifdef TCLX_DEBUG
103 static void
104 ValidateKeyedList _ANSI_ARGS_((keylIntObj_t *keylIntPtr));
105 #endif
106 static int
107 ValidateKey _ANSI_ARGS_((Tcl_Interp *interp, char *key, int keyLen));
108 
109 static keylIntObj_t *
110 AllocKeyedListIntRep _ANSI_ARGS_((void));
111 
112 static void
113 FreeKeyedListData _ANSI_ARGS_((keylIntObj_t *keylIntPtr));
114 
115 static void
116 EnsureKeyedListSpace _ANSI_ARGS_((keylIntObj_t *keylIntPtr,
117 				  int		newNumEntries));
118 
119 static void
120 DeleteKeyedListEntry _ANSI_ARGS_((keylIntObj_t *keylIntPtr,
121 				  int		entryIdx));
122 
123 static intptr_t
124 FindKeyedListEntry _ANSI_ARGS_((keylIntObj_t *keylIntPtr,
125 				const char   *key,
126 				int	     *keyLenPtr,
127 				const char  **nextSubKeyPtr));
128 
129 static void
130 DupKeyedListInternalRep _ANSI_ARGS_((Tcl_Obj *srcPtr,
131 				     Tcl_Obj *copyPtr));
132 
133 static void
134 FreeKeyedListInternalRep _ANSI_ARGS_((Tcl_Obj *keylPtr));
135 
136 static int
137 SetKeyedListFromAny _ANSI_ARGS_((Tcl_Interp *interp,
138 				 Tcl_Obj    *objPtr));
139 
140 static void
141 UpdateStringOfKeyedList _ANSI_ARGS_((Tcl_Obj *keylPtr));
142 
143 static int
144 TclX_KeylgetObjCmd _ANSI_ARGS_((ClientData   clientData,
145 				Tcl_Interp  *interp,
146 				int	     objc,
147 				Tcl_Obj	    *CONST objv[]));
148 
149 static int
150 TclX_KeylsetObjCmd _ANSI_ARGS_((ClientData   clientData,
151 				Tcl_Interp  *interp,
152 				int	     objc,
153 				Tcl_Obj	    *CONST objv[]));
154 
155 static int
156 TclX_KeyldelObjCmd _ANSI_ARGS_((ClientData   clientData,
157 				Tcl_Interp  *interp,
158 				int	     objc,
159 				Tcl_Obj	    *CONST objv[]));
160 
161 static int
162 TclX_KeylkeysObjCmd _ANSI_ARGS_((ClientData   clientData,
163 				 Tcl_Interp  *interp,
164 				 int	      objc,
165 				 Tcl_Obj     *CONST objv[]));
166 
167 /*
168  * Type definition.
169  */
170 static Tcl_ObjType keyedListType = {
171     "keyedList",	      /* name */
172     FreeKeyedListInternalRep, /* freeIntRepProc */
173     DupKeyedListInternalRep,  /* dupIntRepProc */
174     UpdateStringOfKeyedList,  /* updateStringProc */
175     SetKeyedListFromAny	      /* setFromAnyProc */
176 };
177 
178 
179 /*-----------------------------------------------------------------------------
180  * ValidateKeyedList --
181  *   Validate a keyed list (only when TCLX_DEBUG is enabled).
182  * Parameters:
183  *   o keylIntPtr - Keyed list internal representation.
184  *-----------------------------------------------------------------------------
185  */
186 #ifdef TCLX_DEBUG
187 static void
ValidateKeyedList(keylIntPtr)188 ValidateKeyedList (keylIntPtr)
189     keylIntObj_t *keylIntPtr;
190 {
191     int idx;
192 
193     TclX_Assert (keylIntPtr->arraySize >= keylIntPtr->numEntries);
194     TclX_Assert (keylIntPtr->arraySize >= 0);
195     TclX_Assert (keylIntPtr->numEntries >= 0);
196     TclX_Assert ((keylIntPtr->arraySize > 0) ?
197 		 (keylIntPtr->entries != NULL) : TRUE);
198     TclX_Assert ((keylIntPtr->numEntries > 0) ?
199 		 (keylIntPtr->entries != NULL) : TRUE);
200 
201     for (idx = 0; idx < keylIntPtr->numEntries; idx++) {
202 	keylEntry_t *entryPtr = &(keylIntPtr->entries [idx]);
203 	TclX_Assert (entryPtr->key != NULL);
204 	TclX_Assert (entryPtr->valuePtr->refCount >= 1);
205 	if (entryPtr->valuePtr->typePtr == &keyedListType) {
206 	    ValidateKeyedList (entryPtr->valuePtr->internalRep.otherValuePtr);
207 	}
208     }
209 }
210 #endif
211 
212 /*-----------------------------------------------------------------------------
213  * ValidateKey --
214  *   Check that a key or keypath string is a valid value.
215  *
216  * Parameters:
217  *   o interp - Used to return error messages.
218  *   o key - Key string to check.
219  *   o keyLen - Length of the string, used to check for binary data.
220  * Returns:
221  *    TCL_OK or TCL_ERROR.
222  *-----------------------------------------------------------------------------
223  */
224 static int
ValidateKey(interp,key,keyLen)225 ValidateKey (interp, key, keyLen)
226     Tcl_Interp *interp;
227     char *key;
228     int keyLen;
229 {
230     if (strlen (key) != (size_t) keyLen) {
231 	Tcl_AppendStringsToObj(Tcl_GetObjResult(interp),
232 		"keyed list key may not be a binary string", (char *) NULL);
233 	return TCL_ERROR;
234     }
235     if (keyLen == 0) {
236 	Tcl_AppendStringsToObj(Tcl_GetObjResult(interp),
237 		"keyed list key may not be an empty string", (char *) NULL);
238 	return TCL_ERROR;
239     }
240     return TCL_OK;
241 }
242 
243 
244 /*-----------------------------------------------------------------------------
245  * AllocKeyedListIntRep --
246  *   Allocate an and initialize the keyed list internal representation.
247  *
248  * Returns:
249  *    A pointer to the keyed list internal structure.
250  *-----------------------------------------------------------------------------
251  */
252 static keylIntObj_t *
AllocKeyedListIntRep()253 AllocKeyedListIntRep ()
254 {
255     keylIntObj_t *keylIntPtr;
256 
257     keylIntPtr = (keylIntObj_t *) ckalloc (sizeof (keylIntObj_t));
258     memset(keylIntPtr, 0, sizeof (keylIntObj_t));
259 #ifndef NO_KEYLIST_HASH_TABLE
260     keylIntPtr->hashTbl = (Tcl_HashTable *) ckalloc(sizeof(Tcl_HashTable));
261     Tcl_InitHashTable(keylIntPtr->hashTbl, TCL_STRING_KEYS);
262 #endif
263     return keylIntPtr;
264 }
265 
266 /*-----------------------------------------------------------------------------
267  * FreeKeyedListData --
268  *   Free the internal representation of a keyed list.
269  *
270  * Parameters:
271  *   o keylIntPtr - Keyed list internal structure to free.
272  *-----------------------------------------------------------------------------
273  */
274 static void
FreeKeyedListData(keylIntPtr)275 FreeKeyedListData (keylIntPtr)
276     keylIntObj_t *keylIntPtr;
277 {
278     int idx;
279 
280     for (idx = 0; idx < keylIntPtr->numEntries ; idx++) {
281 	ckfree (keylIntPtr->entries [idx].key);
282 	Tcl_DecrRefCount(keylIntPtr->entries [idx].valuePtr);
283     }
284     if (keylIntPtr->entries != NULL)
285 	ckfree ((VOID*) keylIntPtr->entries);
286 #ifndef NO_KEYLIST_HASH_TABLE
287     if (keylIntPtr->hashTbl != NULL) {
288 	Tcl_DeleteHashTable(keylIntPtr->hashTbl);
289 	ckfree((char *) (keylIntPtr->hashTbl));
290     }
291 #endif
292     ckfree ((VOID*) keylIntPtr);
293 }
294 
295 /*-----------------------------------------------------------------------------
296  * EnsureKeyedListSpace --
297  *   Ensure there is enough room in a keyed list array for a certain number
298  * of entries, expanding if necessary.
299  *
300  * Parameters:
301  *   o keylIntPtr - Keyed list internal representation.
302  *   o newNumEntries - The number of entries that are going to be added to
303  *     the keyed list.
304  *-----------------------------------------------------------------------------
305  */
306 static void
EnsureKeyedListSpace(keylIntPtr,newNumEntries)307 EnsureKeyedListSpace (keylIntPtr, newNumEntries)
308     keylIntObj_t *keylIntPtr;
309     int		  newNumEntries;
310 {
311     KEYL_REP_ASSERT (keylIntPtr);
312 
313     if ((keylIntPtr->arraySize - keylIntPtr->numEntries) < newNumEntries) {
314 	int newSize = keylIntPtr->arraySize + newNumEntries +
315 	    KEYEDLIST_ARRAY_INCR_SIZE;
316 	if (keylIntPtr->entries == NULL) {
317 	    keylIntPtr->entries = (keylEntry_t *)
318 		ckalloc (newSize * sizeof (keylEntry_t));
319 	} else {
320 	    keylIntPtr->entries = (keylEntry_t *)
321 		ckrealloc ((VOID *) keylIntPtr->entries,
322 			   newSize * sizeof (keylEntry_t));
323 	}
324 	keylIntPtr->arraySize = newSize;
325     }
326 
327     KEYL_REP_ASSERT (keylIntPtr);
328 }
329 
330 /*-----------------------------------------------------------------------------
331  * DeleteKeyedListEntry --
332  *   Delete an entry from a keyed list.
333  *
334  * Parameters:
335  *   o keylIntPtr - Keyed list internal representation.
336  *   o entryIdx - Index of entry to delete.
337  *-----------------------------------------------------------------------------
338  */
339 static void
DeleteKeyedListEntry(keylIntPtr,entryIdx)340 DeleteKeyedListEntry (keylIntPtr, entryIdx)
341     keylIntObj_t *keylIntPtr;
342     int		  entryIdx;
343 {
344     intptr_t idx;
345 
346 #ifndef NO_KEYLIST_HASH_TABLE
347     if (keylIntPtr->hashTbl != NULL) {
348 	Tcl_HashEntry *entryPtr;
349 	Tcl_HashSearch search;
350 	intptr_t nidx;
351 
352 	entryPtr = Tcl_FindHashEntry(keylIntPtr->hashTbl,
353 		keylIntPtr->entries [entryIdx].key);
354 	if (entryPtr != NULL) {
355 	    Tcl_DeleteHashEntry(entryPtr);
356 	}
357 
358 	/*
359 	 * In order to maintain consistency, we have to iterate over
360 	 * the entire hash table to find and decr relevant idxs.
361 	 * We have to do this even if the previous index was not found
362 	 * in the hash table, as Dup'ing doesn't dup the hash tables.
363 	 */
364 	for (entryPtr = Tcl_FirstHashEntry(keylIntPtr->hashTbl, &search);
365 	     entryPtr != NULL; entryPtr = Tcl_NextHashEntry(&search)) {
366 	    nidx = (intptr_t) Tcl_GetHashValue(entryPtr);
367 	    if (nidx > entryIdx) {
368 		Tcl_SetHashValue(entryPtr, (ClientData) (nidx - 1));
369 	    }
370 	}
371     }
372 #endif
373 
374     ckfree (keylIntPtr->entries [entryIdx].key);
375     Tcl_DecrRefCount(keylIntPtr->entries [entryIdx].valuePtr);
376 
377     for (idx = entryIdx; idx < keylIntPtr->numEntries - 1; idx++)
378 	keylIntPtr->entries [idx] = keylIntPtr->entries [idx + 1];
379     keylIntPtr->numEntries--;
380 
381     KEYL_REP_ASSERT (keylIntPtr);
382 }
383 
384 /*-----------------------------------------------------------------------------
385  * FindKeyedListEntry --
386  *   Find an entry in keyed list.
387  *
388  * Parameters:
389  *   o keylIntPtr - Keyed list internal representation.
390  *   o key - Name of key to search for.
391  *   o keyLenPtr - In not NULL, the length of the key for this
392  *     level is returned here.	This excludes subkeys and the `.' delimiters.
393  *   o nextSubKeyPtr - If not NULL, the start of the name of the next
394  *     sub-key within key is returned.
395  * Returns:
396  *   Index of the entry or -1 if not found.
397  *-----------------------------------------------------------------------------
398  */
399 static intptr_t
FindKeyedListEntry(keylIntPtr,key,keyLenPtr,nextSubKeyPtr)400 FindKeyedListEntry (keylIntPtr, key, keyLenPtr, nextSubKeyPtr)
401     keylIntObj_t *keylIntPtr;
402     const char	 *key;
403     int		 *keyLenPtr;
404     const char	**nextSubKeyPtr;
405 {
406     const char *keySeparPtr;
407     int keyLen;
408     intptr_t findIdx = -1;
409 
410     keySeparPtr = strchr (key, '.');
411     if (keySeparPtr != NULL) {
412 	keyLen = keySeparPtr - key;
413     } else {
414 	keyLen = strlen (key);
415     }
416 
417 #ifndef NO_KEYLIST_HASH_TABLE
418     if (keylIntPtr->hashTbl != NULL) {
419 	Tcl_HashEntry *entryPtr;
420 	char *tmp;
421 	const char *_key;
422 
423 	if (keySeparPtr != NULL) {
424 	    tmp = alloca(keyLen + 1);
425 	    strncpy(tmp, key, keyLen);
426 	    tmp[keyLen] = '\0';
427 	    _key = tmp;
428 	} else
429 	    _key = key;
430 	entryPtr = Tcl_FindHashEntry(keylIntPtr->hashTbl, _key);
431 	if (entryPtr != NULL) {
432 	    findIdx = (intptr_t)Tcl_GetHashValue(entryPtr);
433 	}
434     }
435 #endif
436 
437     if (findIdx == -1) {
438 	for (findIdx = 0; findIdx < keylIntPtr->numEntries; findIdx++) {
439 	    if (keylIntPtr->entries [findIdx].keyLen == keyLen
440 		    && STRNEQU(keylIntPtr->entries [findIdx].key, key, keyLen)) {
441 		break;
442 	    }
443 	}
444     }
445 
446     if (nextSubKeyPtr != NULL) {
447 	if (keySeparPtr == NULL) {
448 	    *nextSubKeyPtr = NULL;
449 	} else {
450 	    *nextSubKeyPtr = keySeparPtr + 1;
451 	}
452     }
453     if (keyLenPtr != NULL) {
454 	*keyLenPtr = keyLen;
455     }
456 
457     if (findIdx >= keylIntPtr->numEntries) {
458 	return -1;
459     }
460 
461     return findIdx;
462 }
463 
464 /*-----------------------------------------------------------------------------
465  * FreeKeyedListInternalRep --
466  *   Free the internal representation of a keyed list.
467  *
468  * Parameters:
469  *   o keylPtr - Keyed list object being deleted.
470  *-----------------------------------------------------------------------------
471  */
472 static void
FreeKeyedListInternalRep(keylPtr)473 FreeKeyedListInternalRep (keylPtr)
474     Tcl_Obj *keylPtr;
475 {
476     FreeKeyedListData ((keylIntObj_t *) keylPtr->internalRep.otherValuePtr);
477 }
478 
479 /*-----------------------------------------------------------------------------
480  * DupKeyedListInternalRep --
481  *   Duplicate the internal representation of a keyed list.
482  *
483  * Parameters:
484  *   o srcPtr - Keyed list object to copy.
485  *   o copyPtr - Target object to copy internal representation to.
486  *-----------------------------------------------------------------------------
487  */
488 static void
DupKeyedListInternalRep(srcPtr,copyPtr)489 DupKeyedListInternalRep (srcPtr, copyPtr)
490     Tcl_Obj *srcPtr;
491     Tcl_Obj *copyPtr;
492 {
493     keylIntObj_t *srcIntPtr =
494 	(keylIntObj_t *) srcPtr->internalRep.otherValuePtr;
495     keylIntObj_t *copyIntPtr;
496     int idx;
497 
498     KEYL_REP_ASSERT (srcIntPtr);
499 
500     copyIntPtr = (keylIntObj_t *) ckalloc (sizeof (keylIntObj_t));
501     copyIntPtr->arraySize = srcIntPtr->arraySize;
502     copyIntPtr->numEntries = srcIntPtr->numEntries;
503     copyIntPtr->entries = (keylEntry_t *)
504 	ckalloc (copyIntPtr->arraySize * sizeof (keylEntry_t));
505 #ifndef NO_KEYLIST_HASH_TABLE
506 #if 0
507     copyIntPtr->hashTbl = (Tcl_HashTable *) ckalloc(sizeof(Tcl_HashTable));
508     Tcl_InitHashTable(copyIntPtr->hashTbl, TCL_STRING_KEYS);
509 #else
510     /*
511      * NO_KEYLIST_HASH_TABLE: We don't duplicate the hash table, so ensure
512      * that consistency checks allow for portions where not all entries are
513      * in the hash table.
514      */
515     copyIntPtr->hashTbl = NULL;
516 #endif
517 #endif
518 
519     for (idx = 0; idx < srcIntPtr->numEntries ; idx++) {
520 	copyIntPtr->entries [idx].key =
521 	    ckstrdup (srcIntPtr->entries [idx].key);
522 	copyIntPtr->entries [idx].keyLen = srcIntPtr->entries [idx].keyLen;
523 	copyIntPtr->entries [idx].valuePtr =
524 	    Tcl_DuplicateObj(srcIntPtr->entries [idx].valuePtr);
525 	Tcl_IncrRefCount(copyIntPtr->entries [idx].valuePtr);
526 #ifndef NO_KEYLIST_HASH_TABLE
527 	/*
528 	 * If we dup the hash table as well and do other better tracking
529 	 * of all access, then we could remove the entries list.
530 	 */
531 #endif
532     }
533 
534     copyPtr->internalRep.otherValuePtr = (VOID *) copyIntPtr;
535     copyPtr->typePtr = &keyedListType;
536 
537     KEYL_REP_ASSERT (copyIntPtr);
538 }
539 
540 /*-----------------------------------------------------------------------------
541  * SetKeyedListFromAny --
542  *   Convert an object to a keyed list from its string representation.	Only
543  * the first level is converted, as there is no way of knowing how far down
544  * the keyed list recurses until lower levels are accessed.
545  *
546  * Parameters:
547  *   o objPtr - Object to convert to a keyed list.
548  *-----------------------------------------------------------------------------
549  */
550 static int
SetKeyedListFromAny(interp,objPtr)551 SetKeyedListFromAny (interp, objPtr)
552     Tcl_Interp *interp;
553     Tcl_Obj    *objPtr;
554 {
555     keylIntObj_t *keylIntPtr;
556     keylEntry_t *keyEntryPtr;
557     char *key;
558     int keyLen, objc, subObjc;
559     intptr_t idx;
560     Tcl_Obj **objv, **subObjv;
561 #ifndef NO_KEYLIST_HASH_TABLE
562     int dummy;
563     Tcl_HashEntry *entryPtr;
564 #endif
565 
566     if (Tcl_ListObjGetElements (interp, objPtr, &objc, &objv) != TCL_OK) {
567 	return TCL_ERROR;
568     }
569 
570     keylIntPtr = AllocKeyedListIntRep();
571 
572     EnsureKeyedListSpace(keylIntPtr, objc);
573 
574     for (idx = 0; idx < objc; idx++) {
575 	if ((Tcl_ListObjGetElements(interp, objv[idx],
576 		     &subObjc, &subObjv) != TCL_OK)
577 		|| (subObjc != 2)) {
578 	    Tcl_ResetResult(interp);
579 	    Tcl_AppendStringsToObj(Tcl_GetObjResult (interp),
580 		    "keyed list entry must be a valid, 2 element list, got \"",
581 		    Tcl_GetString(objv[idx]), "\"", (char *) NULL);
582 	    FreeKeyedListData(keylIntPtr);
583 	    return TCL_ERROR;
584 	}
585 
586 	key = Tcl_GetStringFromObj(subObjv[0], &keyLen);
587 	if (ValidateKey(interp, key, keyLen) == TCL_ERROR) {
588 	    FreeKeyedListData (keylIntPtr);
589 	    return TCL_ERROR;
590 	}
591 	/*
592 	 * When setting from a random list/string, we cannot allow
593 	 * keys to have embedded '.' path separators
594 	 */
595 	if ((strchr(key, '.') != NULL)) {
596 	    Tcl_AppendStringsToObj (Tcl_GetObjResult (interp),
597 		    "keyed list key may not contain a \".\"; ",
598 		    "it is used as a separator in key paths",
599 		    (char *) NULL);
600 	    FreeKeyedListData (keylIntPtr);
601 	    return TCL_ERROR;
602 	}
603 	keyEntryPtr = &(keylIntPtr->entries[idx]);
604 
605 	keyEntryPtr->key = ckstrdup(key);
606 	keyEntryPtr->keyLen = keyLen;
607 	keyEntryPtr->valuePtr = Tcl_DuplicateObj(subObjv[1]);
608 	Tcl_IncrRefCount(keyEntryPtr->valuePtr);
609 #ifndef NO_KEYLIST_HASH_TABLE
610 	entryPtr = Tcl_CreateHashEntry(keylIntPtr->hashTbl,
611 		keyEntryPtr->key, &dummy);
612 	Tcl_SetHashValue(entryPtr, (ClientData) idx);
613 #endif
614 
615 	keylIntPtr->numEntries++;
616     }
617 
618     if ((objPtr->typePtr != NULL) &&
619 	(objPtr->typePtr->freeIntRepProc != NULL)) {
620 	(*objPtr->typePtr->freeIntRepProc) (objPtr);
621     }
622     objPtr->internalRep.otherValuePtr = (VOID *) keylIntPtr;
623     objPtr->typePtr = &keyedListType;
624 
625     KEYL_REP_ASSERT (keylIntPtr);
626     return TCL_OK;
627 }
628 
629 /*-----------------------------------------------------------------------------
630  * UpdateStringOfKeyedList --
631  *    Update the string representation of a keyed list.
632  *
633  * Parameters:
634  *   o objPtr - Object to convert to a keyed list.
635  *-----------------------------------------------------------------------------
636  */
637 static void
UpdateStringOfKeyedList(keylPtr)638 UpdateStringOfKeyedList (keylPtr)
639     Tcl_Obj  *keylPtr;
640 {
641 #define UPDATE_STATIC_SIZE 32
642     int idx, strLen;
643     Tcl_Obj **listObjv, *entryObjv [2], *tmpListObj;
644     Tcl_Obj *staticListObjv [UPDATE_STATIC_SIZE];
645     char *listStr;
646     keylIntObj_t *keylIntPtr =
647 	(keylIntObj_t *) keylPtr->internalRep.otherValuePtr;
648 
649     /*
650      * Conversion to strings is done via list objects to support binary data.
651      */
652     if (keylIntPtr->numEntries > UPDATE_STATIC_SIZE) {
653 	listObjv =
654 	    (Tcl_Obj **) ckalloc (keylIntPtr->numEntries * sizeof (Tcl_Obj *));
655     } else {
656 	listObjv = staticListObjv;
657     }
658 
659     /*
660      * Convert each keyed list entry to a two element list object.  No
661      * need to incr/decr ref counts, the list objects will take care of that.
662      * FIX: Keeping key as string object will speed this up.
663      */
664     for (idx = 0; idx < keylIntPtr->numEntries; idx++) {
665 	entryObjv [0] =
666 	    Tcl_NewStringObj (keylIntPtr->entries [idx].key,
667 		    keylIntPtr->entries [idx].keyLen);
668 	entryObjv [1] = keylIntPtr->entries [idx].valuePtr;
669 	listObjv [idx] = Tcl_NewListObj (2, entryObjv);
670     }
671 
672     tmpListObj = Tcl_NewListObj (keylIntPtr->numEntries, listObjv);
673     Tcl_IncrRefCount(tmpListObj);
674     listStr = Tcl_GetStringFromObj (tmpListObj, &strLen);
675     keylPtr->bytes = ckbinstrdup (listStr, strLen);
676     keylPtr->length = strLen;
677     Tcl_DecrRefCount(tmpListObj);
678 
679     if (listObjv != staticListObjv)
680 	ckfree ((VOID*) listObjv);
681 }
682 
683 /*-----------------------------------------------------------------------------
684  * TclX_NewKeyedListObj --
685  *   Create and initialize a new keyed list object.
686  *
687  * Returns:
688  *    A pointer to the object.
689  *-----------------------------------------------------------------------------
690  */
691 Tcl_Obj *
TclX_NewKeyedListObj()692 TclX_NewKeyedListObj ()
693 {
694     Tcl_Obj *keylPtr = Tcl_NewObj ();
695     keylIntObj_t *keylIntPtr = AllocKeyedListIntRep ();
696 
697     keylPtr->internalRep.otherValuePtr = (VOID *) keylIntPtr;
698     keylPtr->typePtr = &keyedListType;
699     return keylPtr;
700 }
701 
702 /*-----------------------------------------------------------------------------
703  * TclX_KeyedListGet --
704  *   Retrieve a key value from a keyed list.
705  *
706  * Parameters:
707  *   o interp - Error message will be return in result if there is an error.
708  *   o keylPtr - Keyed list object to get key from.
709  *   o key - The name of the key to extract.  Will recusively process sub-keys
710  *     seperated by `.'.
711  *   o valueObjPtrPtr - If the key is found, a pointer to the key object
712  *     is returned here.  NULL is returned if the key is not present.
713  * Returns:
714  *   o TCL_OK - If the key value was returned.
715  *   o TCL_BREAK - If the key was not found.
716  *   o TCL_ERROR - If an error occured.
717  *-----------------------------------------------------------------------------
718  */
719 int
TclX_KeyedListGet(interp,keylPtr,key,valuePtrPtr)720 TclX_KeyedListGet (interp, keylPtr, key, valuePtrPtr)
721     Tcl_Interp *interp;
722     Tcl_Obj    *keylPtr;
723     const char *key;
724     Tcl_Obj   **valuePtrPtr;
725 {
726     keylIntObj_t *keylIntPtr;
727     const char *nextSubKey;
728     int findIdx;
729 
730     while (1) {
731 	if (Tcl_ConvertToType (interp, keylPtr, &keyedListType) != TCL_OK)
732 	    return TCL_ERROR;
733 	keylIntPtr = (keylIntObj_t *) keylPtr->internalRep.otherValuePtr;
734 	KEYL_REP_ASSERT (keylIntPtr);
735 
736 	findIdx = FindKeyedListEntry(keylIntPtr, key, NULL, &nextSubKey);
737 
738 	/*
739 	 * If not found, return status.
740 	 */
741 	if (findIdx < 0) {
742 	    *valuePtrPtr = NULL;
743 	    return TCL_BREAK;
744 	}
745 
746 	/*
747 	 * If we are at the last subkey, return the entry, otherwise recurse
748 	 * down looking for the entry.
749 	 */
750 	if (nextSubKey == NULL) {
751 	    *valuePtrPtr = keylIntPtr->entries [findIdx].valuePtr;
752 	    return TCL_OK;
753 	} else {
754 	    keylPtr = keylIntPtr->entries [findIdx].valuePtr;
755 	    key = nextSubKey;
756 	}
757     }
758 }
759 
760 /*-----------------------------------------------------------------------------
761  * TclX_KeyedListSet --
762  *   Set a key value in keyed list object.
763  *
764  * Parameters:
765  *   o interp - Error message will be return in result object.
766  *   o keylPtr - Keyed list object to update.
767  *   o key - The name of the key to extract.  Will recursively process
768  *     sub-key seperated by `.'.
769  *   o valueObjPtr - The value to set for the key.
770  * Returns:
771  *   TCL_OK or TCL_ERROR.
772  *-----------------------------------------------------------------------------
773  */
774 int
TclX_KeyedListSet(interp,keylPtr,key,valuePtr)775 TclX_KeyedListSet (interp, keylPtr, key, valuePtr)
776     Tcl_Interp *interp;
777     Tcl_Obj    *keylPtr;
778     const char *key;
779     Tcl_Obj    *valuePtr;
780 {
781     keylIntObj_t *keylIntPtr;
782     keylEntry_t *keyEntryPtr;
783     const char *nextSubKey;
784     intptr_t findIdx;
785     int keyLen, status = TCL_OK;
786     Tcl_Obj *newKeylPtr;
787 
788     while (1) {
789 	if (Tcl_ConvertToType (interp, keylPtr, &keyedListType) != TCL_OK)
790 	    return TCL_ERROR;
791 	keylIntPtr = (keylIntObj_t *) keylPtr->internalRep.otherValuePtr;
792 	KEYL_REP_ASSERT (keylIntPtr);
793 
794 	findIdx = FindKeyedListEntry (keylIntPtr, key, &keyLen, &nextSubKey);
795 
796 	/*
797 	 * If we are at the last subkey, either update or add an entry.
798 	 */
799 	if (nextSubKey == NULL) {
800 #ifndef NO_KEYLIST_HASH_TABLE
801 	    int dummy;
802 	    Tcl_HashEntry *entryPtr;
803 #endif
804 	    if (findIdx < 0) {
805 		EnsureKeyedListSpace (keylIntPtr, 1);
806 		findIdx = keylIntPtr->numEntries++;
807 	    } else {
808 		ckfree (keylIntPtr->entries [findIdx].key);
809 		Tcl_DecrRefCount(keylIntPtr->entries [findIdx].valuePtr);
810 	    }
811 	    keyEntryPtr = &(keylIntPtr->entries[findIdx]);
812 	    keyEntryPtr->key = (char *) ckalloc (keyLen + 1);
813 	    memcpy(keyEntryPtr->key, key, keyLen);
814 	    keyEntryPtr->key[keyLen] = '\0';
815 	    keyEntryPtr->keyLen      = keyLen;
816 	    keyEntryPtr->valuePtr    = valuePtr;
817 	    Tcl_IncrRefCount(valuePtr);
818 #ifndef NO_KEYLIST_HASH_TABLE
819 	    if (keylIntPtr->hashTbl == NULL) {
820 		keylIntPtr->hashTbl =
821 		    (Tcl_HashTable *) ckalloc(sizeof(Tcl_HashTable));
822 		Tcl_InitHashTable(keylIntPtr->hashTbl, TCL_STRING_KEYS);
823 	    }
824 	    entryPtr = Tcl_CreateHashEntry(keylIntPtr->hashTbl,
825 		    keyEntryPtr->key, &dummy);
826 	    Tcl_SetHashValue(entryPtr, (ClientData) findIdx);
827 #endif
828 	    Tcl_InvalidateStringRep (keylPtr);
829 
830 	    KEYL_REP_ASSERT (keylIntPtr);
831 	    return TCL_OK;
832 	}
833 
834 	/*
835 	 * If we are not at the last subkey, recurse down, creating new
836 	 * entries if neccessary.  If this level key was not found, it
837 	 * means we must build new subtree. Don't insert the new tree until we
838 	 * come back without error.
839 	 */
840 	if (findIdx >= 0) {
841 	    DupSharedKeyListChild (keylIntPtr, findIdx);
842 	    status = TclX_KeyedListSet (interp,
843 		    keylIntPtr->entries [findIdx].valuePtr,
844 		    nextSubKey, valuePtr);
845 	    if (status == TCL_OK) {
846 		Tcl_InvalidateStringRep (keylPtr);
847 	    }
848 	} else {
849 #ifndef NO_KEYLIST_HASH_TABLE
850 	    int dummy;
851 	    Tcl_HashEntry *entryPtr;
852 #endif
853 	    newKeylPtr = TclX_NewKeyedListObj ();
854 	    Tcl_IncrRefCount(newKeylPtr);
855 	    if (TclX_KeyedListSet (interp, newKeylPtr,
856 			nextSubKey, valuePtr) != TCL_OK) {
857 		Tcl_DecrRefCount(newKeylPtr);
858 		return TCL_ERROR;
859 	    }
860 	    EnsureKeyedListSpace (keylIntPtr, 1);
861 	    findIdx = keylIntPtr->numEntries++;
862 	    keyEntryPtr = &(keylIntPtr->entries[findIdx]);
863 	    keyEntryPtr->key = (char *) ckalloc (keyLen + 1);
864 	    memcpy(keyEntryPtr->key, key, keyLen);
865 	    keyEntryPtr->key[keyLen] = '\0';
866 	    keyEntryPtr->keyLen      = keyLen;
867 	    keyEntryPtr->valuePtr    = newKeylPtr;
868 #ifndef NO_KEYLIST_HASH_TABLE
869 	    if (keylIntPtr->hashTbl == NULL) {
870 		keylIntPtr->hashTbl =
871 		    (Tcl_HashTable *) ckalloc(sizeof(Tcl_HashTable));
872 		Tcl_InitHashTable(keylIntPtr->hashTbl, TCL_STRING_KEYS);
873 	    }
874 	    entryPtr = Tcl_CreateHashEntry(keylIntPtr->hashTbl,
875 		    keyEntryPtr->key, &dummy);
876 	    Tcl_SetHashValue(entryPtr, (ClientData) findIdx);
877 #endif
878 	    Tcl_InvalidateStringRep (keylPtr);
879 	}
880 
881 	KEYL_REP_ASSERT (keylIntPtr);
882 	return status;
883     }
884 }
885 
886 /*-----------------------------------------------------------------------------
887  * TclX_KeyedListDelete --
888  *   Delete a key value from keyed list.
889  *
890  * Parameters:
891  *   o interp - Error message will be return in result if there is an error.
892  *   o keylPtr - Keyed list object to update.
893  *   o key - The name of the key to extract.  Will recusively process
894  *     sub-key seperated by `.'.
895  * Returns:
896  *   o TCL_OK - If the key was deleted.
897  *   o TCL_BREAK - If the key was not found.
898  *   o TCL_ERROR - If an error occured.
899  *-----------------------------------------------------------------------------
900  */
901 int
TclX_KeyedListDelete(interp,keylPtr,key)902 TclX_KeyedListDelete (interp, keylPtr, key)
903     Tcl_Interp *interp;
904     Tcl_Obj    *keylPtr;
905     const char *key;
906 {
907     keylIntObj_t *keylIntPtr, *subKeylIntPtr;
908     const char *nextSubKey;
909     intptr_t findIdx;
910     int status;
911 
912     if (Tcl_ConvertToType (interp, keylPtr, &keyedListType) != TCL_OK)
913 	return TCL_ERROR;
914     keylIntPtr = (keylIntObj_t *) keylPtr->internalRep.otherValuePtr;
915 
916     findIdx = FindKeyedListEntry (keylIntPtr, key, NULL, &nextSubKey);
917 
918     /*
919      * If not found, return status.
920      */
921     if (findIdx < 0) {
922 	KEYL_REP_ASSERT (keylIntPtr);
923 	return TCL_BREAK;
924     }
925 
926     /*
927      * If we are at the last subkey, delete the entry.
928      */
929     if (nextSubKey == NULL) {
930 	DeleteKeyedListEntry (keylIntPtr, findIdx);
931 	Tcl_InvalidateStringRep (keylPtr);
932 
933 	KEYL_REP_ASSERT (keylIntPtr);
934 	return TCL_OK;
935     }
936 
937     /*
938      * If we are not at the last subkey, recurse down.	If the entry is
939      * deleted and the sub-keyed list is empty, delete it as well.  Must
940      * invalidate string, as it caches all representations below it.
941      */
942     DupSharedKeyListChild (keylIntPtr, findIdx);
943 
944     status = TclX_KeyedListDelete (interp,
945 				   keylIntPtr->entries [findIdx].valuePtr,
946 				   nextSubKey);
947     if (status == TCL_OK) {
948 	subKeylIntPtr = (keylIntObj_t *)
949 	    keylIntPtr->entries [findIdx].valuePtr->internalRep.otherValuePtr;
950 	if (subKeylIntPtr->numEntries == 0) {
951 	    DeleteKeyedListEntry (keylIntPtr, findIdx);
952 	}
953 	Tcl_InvalidateStringRep (keylPtr);
954     }
955 
956     KEYL_REP_ASSERT (keylIntPtr);
957     return status;
958 }
959 
960 /*-----------------------------------------------------------------------------
961  * TclX_KeyedListGetKeys --
962  *   Retrieve a list of keyed list keys.
963  *
964  * Parameters:
965  *   o interp - Error message will be return in result if there is an error.
966  *   o keylPtr - Keyed list object to get key from.
967  *   o key - The name of the key to get the sub keys for.  NULL or empty
968  *     to retrieve all top level keys.
969  *   o listObjPtrPtr - List object is returned here with key as values.
970  * Returns:
971  *   o TCL_OK - If the zero or more key where returned.
972  *   o TCL_BREAK - If the key was not found.
973  *   o TCL_ERROR - If an error occured.
974  *-----------------------------------------------------------------------------
975  */
976 int
TclX_KeyedListGetKeys(interp,keylPtr,key,listObjPtrPtr)977 TclX_KeyedListGetKeys (interp, keylPtr, key, listObjPtrPtr)
978     Tcl_Interp *interp;
979     Tcl_Obj    *keylPtr;
980     const char *key;
981     Tcl_Obj   **listObjPtrPtr;
982 {
983     keylIntObj_t *keylIntPtr;
984     Tcl_Obj *listObjPtr;
985     const char *nextSubKey;
986     int idx;
987     intptr_t findIdx;
988 
989     if (Tcl_ConvertToType (interp, keylPtr, &keyedListType) != TCL_OK)
990 	return TCL_ERROR;
991     keylIntPtr = (keylIntObj_t *) keylPtr->internalRep.otherValuePtr;
992 
993     /*
994      * If key is not NULL or empty, then recurse down until we go past
995      * the end of all of the elements of the key.
996      */
997     if ((key != NULL) && (key [0] != '\0')) {
998 	findIdx = FindKeyedListEntry (keylIntPtr, key, NULL, &nextSubKey);
999 	if (findIdx < 0) {
1000 	    TclX_Assert (keylIntPtr->arraySize >= keylIntPtr->numEntries);
1001 	    return TCL_BREAK;
1002 	}
1003 	TclX_Assert (keylIntPtr->arraySize >= keylIntPtr->numEntries);
1004 	return TclX_KeyedListGetKeys (interp,
1005 				      keylIntPtr->entries [findIdx].valuePtr,
1006 				      nextSubKey,
1007 				      listObjPtrPtr);
1008     }
1009 
1010     /*
1011      * Reached the end of the full key, return all keys at this level.
1012      */
1013     listObjPtr = Tcl_NewObj();
1014     for (idx = 0; idx < keylIntPtr->numEntries; idx++) {
1015 	Tcl_ListObjAppendElement(interp, listObjPtr,
1016 		Tcl_NewStringObj(keylIntPtr->entries[idx].key,
1017 			keylIntPtr->entries[idx].keyLen));
1018     }
1019     *listObjPtrPtr = listObjPtr;
1020     TclX_Assert (keylIntPtr->arraySize >= keylIntPtr->numEntries);
1021     return TCL_OK;
1022 }
1023 
1024 /*-----------------------------------------------------------------------------
1025  * Tcl_KeylgetObjCmd --
1026  *     Implements the TCL keylget command:
1027  *	   keylget listvar ?key? ?retvar | {}?
1028  *-----------------------------------------------------------------------------
1029  */
1030 static int
TclX_KeylgetObjCmd(clientData,interp,objc,objv)1031 TclX_KeylgetObjCmd (clientData, interp, objc, objv)
1032     ClientData	 clientData;
1033     Tcl_Interp	*interp;
1034     int		 objc;
1035     Tcl_Obj	*CONST objv[];
1036 {
1037     Tcl_Obj *keylPtr, *valuePtr;
1038     char *key;
1039     int keyLen, status;
1040 
1041     if ((objc < 2) || (objc > 4)) {
1042 	return TclX_WrongArgs (interp, objv [0],
1043 			       "listvar ?key? ?retvar | {}?");
1044     }
1045 
1046     /*
1047      * Handle request for list of keys, use keylkeys command.
1048      */
1049     if (objc == 2)
1050 	return TclX_KeylkeysObjCmd (clientData, interp, objc, objv);
1051 
1052     keylPtr = Tcl_ObjGetVar2(interp, objv[1], NULL, TCL_LEAVE_ERR_MSG);
1053     if (keylPtr == NULL) {
1054 	return TCL_ERROR;
1055     }
1056 
1057     /*
1058      * Handle retrieving a value for a specified key.
1059      */
1060     key = Tcl_GetStringFromObj (objv [2], &keyLen);
1061     if (ValidateKey(interp, key, keyLen) == TCL_ERROR) {
1062 	return TCL_ERROR;
1063     }
1064 
1065     status = TclX_KeyedListGet (interp, keylPtr, key, &valuePtr);
1066     if (status == TCL_ERROR)
1067 	return TCL_ERROR;
1068 
1069     /*
1070      * Handle key not found.
1071      */
1072     if (status == TCL_BREAK) {
1073 	if (objc == 3) {
1074 	    TclX_AppendObjResult (interp, "key \"",  key,
1075 		    "\" not found in keyed list", (char *) NULL);
1076 	    return TCL_ERROR;
1077 	} else {
1078 	    Tcl_SetBooleanObj (Tcl_GetObjResult (interp), FALSE);
1079 	    return TCL_OK;
1080 	}
1081     }
1082 
1083     /*
1084      * No variable specified, so return value in the result.
1085      */
1086     if (objc == 3) {
1087 	Tcl_SetObjResult (interp, valuePtr);
1088 	return TCL_OK;
1089     }
1090 
1091     /*
1092      * Variable (or empty variable name) specified.
1093      */
1094     if (!TclX_IsNullObj(objv [3]) &&
1095 	    (Tcl_ObjSetVar2(interp, objv [3], NULL, valuePtr,
1096 		    TCL_LEAVE_ERR_MSG) == NULL)) {
1097 	return TCL_ERROR;
1098     }
1099     Tcl_SetBooleanObj (Tcl_GetObjResult (interp), TRUE);
1100     return TCL_OK;
1101 }
1102 
1103 /*-----------------------------------------------------------------------------
1104  * Tcl_KeylsetObjCmd --
1105  *     Implements the TCL keylset command:
1106  *	   keylset listvar key value ?key value...?
1107  *-----------------------------------------------------------------------------
1108  */
1109 static int
TclX_KeylsetObjCmd(clientData,interp,objc,objv)1110 TclX_KeylsetObjCmd (clientData, interp, objc, objv)
1111     ClientData	 clientData;
1112     Tcl_Interp	*interp;
1113     int		 objc;
1114     Tcl_Obj	*CONST objv[];
1115 {
1116     Tcl_Obj *keylVarPtr, *newVarObj;
1117     char *key;
1118     int idx, keyLen, result = TCL_OK;
1119 
1120     if ((objc < 4) || ((objc % 2) != 0)) {
1121 	return TclX_WrongArgs (interp, objv [0],
1122 			       "listvar key value ?key value...?");
1123     }
1124 
1125     /*
1126      * Get the variable that we are going to update.  If the var doesn't exist,
1127      * create it.  If it is shared by more than being a variable, duplicated
1128      * it.
1129      */
1130     keylVarPtr = Tcl_ObjGetVar2(interp, objv[1], NULL, 0);
1131     if (keylVarPtr == NULL) {
1132 	newVarObj = keylVarPtr = TclX_NewKeyedListObj();
1133 	Tcl_IncrRefCount(newVarObj);
1134     } else if (Tcl_IsShared(keylVarPtr)) {
1135 	newVarObj = keylVarPtr = Tcl_DuplicateObj(keylVarPtr);
1136 	Tcl_IncrRefCount(newVarObj);
1137     } else {
1138 	newVarObj = NULL;
1139     }
1140 
1141     for (idx = 2; idx < objc; idx += 2) {
1142 	key = Tcl_GetStringFromObj (objv [idx], &keyLen);
1143 	if ((ValidateKey(interp, key, keyLen) == TCL_ERROR)
1144 		|| (TclX_KeyedListSet (interp, keylVarPtr, key, objv [idx+1])
1145 			!= TCL_OK)) {
1146 	    result = TCL_ERROR;
1147 	    break;
1148 	}
1149     }
1150 
1151     if ((result == TCL_OK) &&
1152 	    (Tcl_ObjSetVar2(interp, objv[1], NULL, keylVarPtr,
1153 		    TCL_LEAVE_ERR_MSG) == NULL)) {
1154 	result = TCL_ERROR;
1155     }
1156 
1157     if (newVarObj != NULL) {
1158 	Tcl_DecrRefCount(newVarObj);
1159     }
1160     return result;
1161 }
1162 
1163 /*-----------------------------------------------------------------------------
1164  * Tcl_KeyldelObjCmd --
1165  *     Implements the TCL keyldel command:
1166  *	   keyldel listvar key ?key ...?
1167  *----------------------------------------------------------------------------
1168  */
1169 static int
TclX_KeyldelObjCmd(clientData,interp,objc,objv)1170 TclX_KeyldelObjCmd (clientData, interp, objc, objv)
1171     ClientData	 clientData;
1172     Tcl_Interp	*interp;
1173     int		 objc;
1174     Tcl_Obj	*CONST objv[];
1175 {
1176     Tcl_Obj *keylVarPtr, *keylPtr;
1177     char *key;
1178     int idx, keyLen, status;
1179 
1180     if (objc < 3) {
1181 	return TclX_WrongArgs (interp, objv [0], "listvar key ?key ...?");
1182     }
1183 
1184     /*
1185      * Get the variable that we are going to update.  If it is shared by more
1186      * than being a variable, duplicated it.
1187      */
1188     keylVarPtr = Tcl_ObjGetVar2(interp, objv[1], NULL, TCL_LEAVE_ERR_MSG);
1189     if (keylVarPtr == NULL) {
1190 	return TCL_ERROR;
1191     }
1192     if (Tcl_IsShared (keylVarPtr)) {
1193 	keylPtr = Tcl_DuplicateObj (keylVarPtr);
1194 	keylVarPtr = Tcl_ObjSetVar2(interp, objv[1], NULL, keylPtr,
1195 				   TCL_LEAVE_ERR_MSG);
1196 	if (keylVarPtr == NULL) {
1197 	    Tcl_DecrRefCount(keylPtr);
1198 	    return TCL_ERROR;
1199 	}
1200 	if (keylVarPtr != keylPtr)
1201 	    Tcl_DecrRefCount(keylPtr);
1202     }
1203     keylPtr = keylVarPtr;
1204 
1205     for (idx = 2; idx < objc; idx++) {
1206 	key = Tcl_GetStringFromObj (objv [idx], &keyLen);
1207 	if (ValidateKey(interp, key, keyLen) == TCL_ERROR) {
1208 	    return TCL_ERROR;
1209 	}
1210 
1211 	status = TclX_KeyedListDelete (interp, keylPtr, key);
1212 	switch (status) {
1213 	  case TCL_BREAK:
1214 	    TclX_AppendObjResult (interp, "key not found: \"",
1215 				  key, "\"", (char *) NULL);
1216 	    return TCL_ERROR;
1217 	  case TCL_ERROR:
1218 	    return TCL_ERROR;
1219 	}
1220     }
1221 
1222     return TCL_OK;
1223 }
1224 
1225 /*-----------------------------------------------------------------------------
1226  * Tcl_KeylkeysObjCmd --
1227  *     Implements the TCL keylkeys command:
1228  *	   keylkeys listvar ?key?
1229  *-----------------------------------------------------------------------------
1230  */
1231 static int
TclX_KeylkeysObjCmd(clientData,interp,objc,objv)1232 TclX_KeylkeysObjCmd (clientData, interp, objc, objv)
1233     ClientData	 clientData;
1234     Tcl_Interp	*interp;
1235     int		 objc;
1236     Tcl_Obj	*CONST objv[];
1237 {
1238     Tcl_Obj *keylPtr, *listObjPtr;
1239     char *key;
1240     int keyLen, status;
1241 
1242     if ((objc < 2) || (objc > 3)) {
1243 	return TclX_WrongArgs (interp, objv [0], "listvar ?key?");
1244     }
1245 
1246     keylPtr = Tcl_ObjGetVar2(interp, objv[1], NULL, TCL_LEAVE_ERR_MSG);
1247     if (keylPtr == NULL) {
1248 	return TCL_ERROR;
1249     }
1250 
1251     /*
1252      * If key argument is not specified, then objv [2] is NULL or empty,
1253      * meaning get top level keys.
1254      */
1255     if (objc < 3) {
1256 	key = NULL;
1257     } else {
1258 	key = Tcl_GetStringFromObj (objv [2], &keyLen);
1259 	if (ValidateKey(interp, key, keyLen) == TCL_ERROR) {
1260 	    return TCL_ERROR;
1261 	}
1262     }
1263 
1264     status = TclX_KeyedListGetKeys (interp, keylPtr, key, &listObjPtr);
1265     switch (status) {
1266       case TCL_BREAK:
1267 	TclX_AppendObjResult (interp, "key not found: \"", key, "\"",
1268 			      (char *) NULL);
1269 	return TCL_ERROR;
1270       case TCL_ERROR:
1271 	return TCL_ERROR;
1272     }
1273 
1274     Tcl_SetObjResult (interp, listObjPtr);
1275 
1276     return TCL_OK;
1277 }
1278 
1279 /*-----------------------------------------------------------------------------
1280  * TclX_KeyedListInit --
1281  *   Initialize the keyed list commands for this interpreter.
1282  *
1283  * Parameters:
1284  *   o interp - Interpreter to add commands to.
1285  *-----------------------------------------------------------------------------
1286  */
1287 void
TclX_KeyedListInit(interp)1288 TclX_KeyedListInit (interp)
1289     Tcl_Interp *interp;
1290 {
1291     Tcl_RegisterObjType (&keyedListType);
1292 
1293     Tcl_CreateObjCommand (interp, "keylget", TclX_KeylgetObjCmd,
1294 	    (ClientData) NULL, (Tcl_CmdDeleteProc*) NULL);
1295 
1296     Tcl_CreateObjCommand (interp, "keylset", TclX_KeylsetObjCmd,
1297 	    (ClientData) NULL, (Tcl_CmdDeleteProc*) NULL);
1298 
1299     Tcl_CreateObjCommand (interp, "keyldel", TclX_KeyldelObjCmd,
1300 	    (ClientData) NULL, (Tcl_CmdDeleteProc*) NULL);
1301 
1302     Tcl_CreateObjCommand (interp, "keylkeys", TclX_KeylkeysObjCmd,
1303 	    (ClientData) NULL, (Tcl_CmdDeleteProc*) NULL);
1304 }
1305 
1306 
1307