/* vi:set ts=8 sts=4 sw=4 noet:
*
* VIM - Vi IMproved by Bram Moolenaar
*
* Do ":help uganda" in Vim to read copying and usage conditions.
* Do ":help credits" in Vim to see a list of people who contributed.
* See README.txt for an overview of the Vim source code.
*/
/*
* dict.c: Dictionary support
*/
#include "vim.h"
#if defined(FEAT_EVAL) || defined(PROTO)
/* List head for garbage collection. Although there can be a reference loop
* from partial to dict to partial, we don't need to keep track of the partial,
* since it will get freed when the dict is unused and gets freed. */
static dict_T *first_dict = NULL; /* list of all dicts */
/*
* Allocate an empty header for a dictionary.
*/
dict_T *
dict_alloc(void)
{
dict_T *d;
d = (dict_T *)alloc(sizeof(dict_T));
if (d != NULL)
{
/* Add the dict to the list of dicts for garbage collection. */
if (first_dict != NULL)
first_dict->dv_used_prev = d;
d->dv_used_next = first_dict;
d->dv_used_prev = NULL;
first_dict = d;
hash_init(&d->dv_hashtab);
d->dv_lock = 0;
d->dv_scope = 0;
d->dv_refcount = 0;
d->dv_copyID = 0;
}
return d;
}
/*
* dict_alloc() with an ID for alloc_fail().
*/
dict_T *
dict_alloc_id(alloc_id_T id UNUSED)
{
#ifdef FEAT_EVAL
if (alloc_fail_id == id && alloc_does_fail(sizeof(list_T)))
return NULL;
#endif
return (dict_alloc());
}
dict_T *
dict_alloc_lock(int lock)
{
dict_T *d = dict_alloc();
if (d != NULL)
d->dv_lock = lock;
return d;
}
/*
* Allocate an empty dict for a return value.
* Returns OK or FAIL.
*/
int
rettv_dict_alloc(typval_T *rettv)
{
dict_T *d = dict_alloc_lock(0);
if (d == NULL)
return FAIL;
rettv_dict_set(rettv, d);
return OK;
}
/*
* Set a dictionary as the return value
*/
void
rettv_dict_set(typval_T *rettv, dict_T *d)
{
rettv->v_type = VAR_DICT;
rettv->vval.v_dict = d;
if (d != NULL)
++d->dv_refcount;
}
/*
* Free a Dictionary, including all non-container items it contains.
* Ignores the reference count.
*/
void
dict_free_contents(dict_T *d)
{
int todo;
hashitem_T *hi;
dictitem_T *di;
/* Lock the hashtab, we don't want it to resize while freeing items. */
hash_lock(&d->dv_hashtab);
todo = (int)d->dv_hashtab.ht_used;
for (hi = d->dv_hashtab.ht_array; todo > 0; ++hi)
{
if (!HASHITEM_EMPTY(hi))
{
/* Remove the item before deleting it, just in case there is
* something recursive causing trouble. */
di = HI2DI(hi);
hash_remove(&d->dv_hashtab, hi);
dictitem_free(di);
--todo;
}
}
/* The hashtab is still locked, it has to be re-initialized anyway */
hash_clear(&d->dv_hashtab);
}
static void
dict_free_dict(dict_T *d)
{
/* Remove the dict from the list of dicts for garbage collection. */
if (d->dv_used_prev == NULL)
first_dict = d->dv_used_next;
else
d->dv_used_prev->dv_used_next = d->dv_used_next;
if (d->dv_used_next != NULL)
d->dv_used_next->dv_used_prev = d->dv_used_prev;
vim_free(d);
}
static void
dict_free(dict_T *d)
{
if (!in_free_unref_items)
{
dict_free_contents(d);
dict_free_dict(d);
}
}
/*
* Unreference a Dictionary: decrement the reference count and free it when it
* becomes zero.
*/
void
dict_unref(dict_T *d)
{
if (d != NULL && --d->dv_refcount <= 0)
dict_free(d);
}
/*
* Go through the list of dicts and free items without the copyID.
* Returns TRUE if something was freed.
*/
int
dict_free_nonref(int copyID)
{
dict_T *dd;
int did_free = FALSE;
for (dd = first_dict; dd != NULL; dd = dd->dv_used_next)
if ((dd->dv_copyID & COPYID_MASK) != (copyID & COPYID_MASK))
{
/* Free the Dictionary and ordinary items it contains, but don't
* recurse into Lists and Dictionaries, they will be in the list
* of dicts or list of lists. */
dict_free_contents(dd);
did_free = TRUE;
}
return did_free;
}
void
dict_free_items(int copyID)
{
dict_T *dd, *dd_next;
for (dd = first_dict; dd != NULL; dd = dd_next)
{
dd_next = dd->dv_used_next;
if