/* Class 'Dict' -- General Symbol Table Functions
 * To manage <key, value> pairs.
 *
 * <key> is a C string.  <value> is a pointer to an arbitrary object.
 */

/* TODO
 *
 * [X]	expansion and re-hash when full
 *
 * [X]	provide hooks for hash, compare functions
 *
 * [X]	provide sequencing over all elements.
 */

#include <stdio.h>
#include <string.h>
#include <strings.h>
extern char*	strdup();
extern int	strcasecmp();

#define _CLASS_Dict_PRIVATE_
#include "dict.h"		/* import public declarations */

/* Symtab:
 *
 * Private declarations
 */

/* Type shorthands */
typedef dict_key_t	Key_t ;
typedef dict_value_t	Value_t;
typedef dict_hash_t	Hash_t;


/* SymEnt is a private structure used only by internal functions */
typedef struct _SymEnt_* SymEnt;
struct	_SymEnt_ {
	Key_t	name;
	Value_t	value;
	Hash_t	hash;
	SymEnt	next;
};

/* Private Dict functions */
#if defined (__STDC__)

	static Dict dict_expand (Dict self);
	static SymEnt*	dict_locationOf (
				Dict	self,
				SymEnt	assoc,
				int	mayAdd
			);
	static SymEnt syment_new (void);
	static SymEnt syment_dispose (SymEnt sep);
	static Hash_t	symhash (Key_t key);
	static Hash_t	symcasehash (Key_t key);

#else

	static Dict dict_expand ();
	static SymEnt*	dict_locationOf ();
	static SymEnt syment_new ();
	static SymEnt syment_dispose ();
	static Hash_t	symhash ();
	static Hash_t	symcasehash ();

#endif

#define DFLTHASH	127		/* initial hash array size */


/* Manage private pool of SymEnt structures
 */
static SymEnt _freehd;			/* free list head */
#define DFLTSYMENT	64		/* # SymEnt struct to grab in a hunk */

#define GETSEP(SEP)	(_freehd ? \
			 (((SEP) = _freehd), _freehd = _freehd->next, (SEP)) \
			 : ((SEP) = syment_new()))
#define PUTSEP(SEP)	((SEP)->next = _freehd, _freehd = (SEP))
#define CLRSEP(SEP)	((SEP)->name=0,(SEP)->value=0,(SEP)->hash=0)


/* Even though declared above for clarity, this is a macro
 */
#define syment_dispose(SEP)	(free((SEP)->name),CLRSEP(SEP),PUTSEP(SEP),0)



/* Instance Creation, Deletion:
 *
 *	new(), dispose()
 */

/* Create a new symbol table and initialize it for business
 */
Dict
dict_new()
{
	Dict s;

	s = (Dict)calloc (1, sizeof *s);
	s->f_hash = symcasehash;
	s->f_compare = strcasecmp;
	s->nhead = DFLTHASH;
	s->head = (SymEnt*)calloc (s->nhead, sizeof(SymEnt));
	return s;
}

/* Dispose of a dictionary, freeing all the memory allocated
 * to it.
 *
 * N.B.: this does not free memory allocated to the values in
 * the table.
 */
Dict
dict_dispose (self)
Dict	self;
{
	register SymEnt* psep, sep, nsep;
	register int i;

	if (self == 0)
		return 0;

	psep = &self->head[0];
	for (i = self->nhead; i > 0; i--, psep++) {
		for (sep = *psep; sep; sep = nsep) {
			nsep = sep->next;
			syment_dispose (sep);
		}
		*psep = 0;
	}
	free (self->head);
	free (self);
	return 0;
}

int
dict_size (self)
Dict	self;
{
	return self->nitems;
}


/* Accessing:
 *
 *	add(), remove(), find(), replace(), keys(), values()
 */

/*
 * Add element <v> to dictionary with key <n>.
 *
 * Return current value stored at this key if one exists,
 * otherwise, add new <key,value> association.
 *
 */
Value_t
dict_add (self, n, v)
Dict	self;
Key_t	n;
Value_t	v;
{
	SymEnt* psep, sep;
	struct _SymEnt_ tmp;
	Hash_t hsh;

	tmp.name = n;
	psep = dict_locationOf (self, &tmp, 1);
	if (psep == 0)
		return 0;

	else if (tmp.value != 0)
		return tmp.value;


	GETSEP(sep);
	sep->hash = tmp.hash;
	sep->name = strdup (n);
	sep->value = v;

	/* link it in at the front
	 * If this is the first element on this chain, increment
	 * the count of heads in use.
	 */
	if ((sep->next = *psep) == 0)
		self->nheadinuse++;
	*psep = sep;
	self->nitems++;

	return 0;
}

/*
 * Remove element with key <n> from symbol table <self>
 *
 * Return value associated with that key (if any).
 *
 */
Value_t
dict_remove (self, n)
Dict	self;
Key_t	n;
{
	register SymEnt* psep, sep;
	struct _SymEnt_ tmp;
	Value_t v = 0;

	tmp.name = n;
	psep = dict_locationOf (self, &tmp, 0);
	if (psep == 0 || (sep = *psep) == 0 || tmp.value == 0)
		return 0;

	self->nitems--;
	*psep = sep->next;
	if (psep >= self->head && psep < &self->head[self->nhead])
		self->nheadinuse--;
	syment_dispose(sep);
	return tmp.value;
}

/*
 * Find element matching key <n> in symbol table <self>
 */
Value_t
dict_find (self, n)
Dict	self;
Key_t	n;
{
	register SymEnt* psep;
	struct _SymEnt_ tmp;

	tmp.name = n;
	psep = dict_locationOf (self, &tmp, 0);

	return psep ? tmp.value : 0;
}

/*
 * Returns the actual key of the entry matching argument key.
 * Useful sometimes (with care) when you want to use the value
 * in dynamically allocated key storage without duplicating
 * the string.
 *
 * Any modification of the return value is disasterous.
 */

Key_t
dict_findKey (self, key)
Dict	self;
Key_t	key;
{
	register SymEnt* psep;
	struct _SymEnt_ tmp;

	tmp.name = key;
	psep = dict_locationOf (self, &tmp, 0);

	return psep ? tmp.name : 0;
}

/* Like add above, but replace any existing value
 */
Value_t
dict_replace (self, n, v)
Dict	self;
Key_t	n;
Value_t	v;
{
	SymEnt* psep, sep;
	struct _SymEnt_ tmp;
	Hash_t hsh;
	Value_t oldValue;


	tmp.name = n;
	psep = dict_locationOf (self, &tmp, 1);
	if (psep == 0)
		return 0;

	/* If key matches existing entry, just replace the old
	 * value with the new one and return it.
	 */
	else if ((oldValue = tmp.value) != 0) {
		(*psep)->value = v;
		return oldValue;
	}


	/* Otherwise, we have to add the association
	 */
	GETSEP(sep);
	*sep = tmp;
	sep->name = strdup (sep->name);
	sep->value = v;

	/* link it in at the front
	 * If this is the first element on this chain, increment
	 * the count of heads in use.
	 */
	if ((sep->next = *psep) == 0)
		self->nheadinuse++;
	*psep = sep;
	self->nitems++;

	return 0;
}

/* Return list of keys in this Dict
 */
Key_t*
dict_keys (self)
Dict	self;
{
	Key_t* retvec;
	register int i, j;
	register SymEnt* psep, sep;

	retvec = (Key_t*)calloc (self->nitems+1, sizeof (Key_t));
	if (retvec == 0) {
		self->error = "Allocation failure";
		return 0;
	}
	j = 0;

	for (i = self->nhead, psep = self->head; i > 0; i--, psep++) {
		for (sep = *psep; sep != 0; sep = sep->next) {
			retvec[j++] = sep->name;
		}
	}
	return retvec;
}

/* Return list of values in this Dict
 */
Value_t*
dict_values (self)
Dict	self;
{
	Value_t* retvec;
	register int i, j;
	register SymEnt* psep, sep;

	retvec = (Value_t*)calloc (self->nitems+1, sizeof (Value_t));
	if (retvec == 0) {
		self->error = "Allocation failure";
		return 0;
	}
	j = 0;

	for (i = self->nhead, psep = self->head; i > 0; i--, psep++) {
		for (sep = *psep; sep != 0; sep = sep->next) {
			retvec[j++] = sep->value;
		}
	}
	return retvec;
}

/* Miscellaneous:
 *
 *	ignoreCase(), printOn()
 */

/* Set up to either respect or ignore case distinctions
 * in keys.
 */
int
dict_ignoreCase (self, yesno)
Dict	self;
int	yesno;
{
	/* Can't do this if there's stuff already in the Dict
	 */
	if (self->nitems > 0) {
		self->error = "Dictionary not empty";
		return -1;
	}

	/* Ignore case on key comparisons */
	if (yesno) {
		self->f_hash = symcasehash;
		self->f_compare = strcasecmp;
	}
	else {
		self->f_hash = symhash;
		self->f_compare = strcmp;
	}
	return 0;
}

dict_printOn (self, file)
Dict	self;
FILE*	file;
{
	fprintf (file, "Dict(0x%x): nhead=%d; nheadinuse=%d\n",
		 self, self->nhead, self->nheadinuse);
	fprintf (file, "  nitems=%d; avgChain = %.2f\n", self->nitems,
		self->nheadinuse ? (double)self->nitems/self->nheadinuse : (double )0);
}

/* Private Functions:
 *
 *	expand(), symhash(), symcasehash(), locationOf()
 */

/* Expand dictionary by 50% or DFLTHASH, whichever is smaller
 *
 * This may invert the order in a list of two keys which hash to 
 * the same location.
 */
static Dict
dict_expand (self)
Dict self;
{
	SymEnt* oldhead, p, n;
	SymEnt* psep;
	register int i;
	int oldnhead;
	int newnhead = self->nhead/2;

	if (newnhead > DFLTHASH)
		newnhead = DFLTHASH;
	newnhead += self->nhead;

	/* Save current parameters */
	oldhead = self->head;
	oldnhead = self->nhead;

	/* allocate larger list array */
	self->nhead = newnhead;
	self->head = (SymEnt*)calloc (newnhead, sizeof (SymEnt));
	self->nheadinuse = 0;

	/* move current contents to new list */
	for (i = 0; i < oldnhead; i++) {
		for (p = oldhead[i]; p; p = n) {
			n = p->next;
			psep = &self->head[p->hash % self->nhead];
			if ((p->next = *psep) == 0)
				self->nheadinuse++;
			*psep = p;
		}
	}

	/* free the old list head */
	free (oldhead);
	return self;
}

/* Compute a simple hash function on a string
 * This one is case-sensitive.
 */
static Hash_t
symhash (ptr)
Key_t	ptr;
{
	Hash_t ret = 0;
	register unsigned char* nhp = (unsigned char*)ptr;

	while (*nhp) {
		ret = (ret << 1) + *nhp++;
		if (ret & 0x8000) {
			ret &= 0x7fff;
			ret++;
		}
	}
	return ret;
}

/* As above, but case-insensitive
 */
#include <ctype.h>
static Hash_t
symcasehash (ptr)
Key_t	ptr;
{
	Hash_t ret = 0;
	register unsigned char* nhp = (unsigned char*)ptr;
	register cv;

	while (*nhp) {
		cv = *nhp++;
		if (isupper(cv))
			cv = tolower(cv);
		ret = (ret << 1) + cv;
		if (ret & 0x8000) {
			ret &= 0x7fff;
			ret++;
		}
	}
	return ret;
}

/* Return the location of the Assoc identified by <key>
 * This is can be used to return an existing entry, replace
 * an existing entry, or add a new entry.
 *
 * This is the common function supporting find, add, replace,
 * and delete operations.
 */
static SymEnt*
dict_locationOf (self, assoc, mayAdd)
Dict	self;
SymEnt	assoc;
int	mayAdd;
{
	register Hash_t hsh;
	register SymEnt* psep, *newsep, sep;

	if (self == 0 || assoc == 0 || assoc->name == 0) {
		if (self)
			self->error = "NIL argument(s)";
		return 0;
	}

	/* See if table should be expanded */
	if (mayAdd && self->nheadinuse
	    && ((self->nitems / self->nheadinuse) >= 4
	    || self->nheadinuse >= ((self->nhead*4)/5)))
		self = dict_expand (self);

	hsh = self->f_hash(assoc->name);
	assoc->hash = hsh;
	assoc->value = 0;

	newsep = &self->head[hsh % self->nhead];

	for ( psep = newsep; (sep = *psep) != 0; psep = &sep->next) {
		if (sep->hash == hsh
		    && (assoc->value == sep->value
			|| self->f_compare(sep->name, assoc->name) == 0)) {
			assoc->name = sep->name;
			assoc->value = sep->value;
			return psep;
		}
	}
	self->error = "element not found";
	return newsep;
}


/* Get another glob of SymEnts and stick them on a local free list
 */
static SymEnt
syment_new()
{
	register SymEnt sep;
	register n;

	sep = (SymEnt) calloc (DFLTSYMENT, sizeof (*sep));
	for (n = 0; n < DFLTSYMENT; n++, sep++)
		PUTSEP(sep);

	GETSEP(sep);
	return sep;
}

#ifdef TEST

struct DATA {
	char* name; int value;
} init_data[] ={
	"red",		0x01,
	"white",	0x02,
	"bleu",		0x03,
	"orange",	0x04,
	"lavender",	0x14,
	"purple",	0x24,
	"fuscia",	0x34,
	"aqua",		0x44,
	"brown",	0x54,
	"green",	0x64,
	"turquoise",	0x74,
	"yellow",	0x84,
	"beige",	0x94,
	"pink",		0xa4,
	"black",	0xb4,
	"cyan",		0xc4,
	"magenta",	0xd4,
	"ruby",		0xe4,
	"carnelian",	0xf4,
	"plum",		0x15,
	"saffron",	0x25,
	"wheat",	0x35,
	{ 0, 0 }
};

main(argc, argv)
int	argc;
char**	argv;
{
	Dict s;
	int i;
	void* v;

	s = dict_new();

	/* Test insertion */
	for (i = 0; init_data[i].name; i++) {
		v = dict_add (s, init_data[i].name,
			(void*)init_data[i].value);
		printf ("Adding <'%s',%x> yields %x\n",
			init_data[i].name, init_data[i].value, v);
	}
	dict_printOn(s, stdout);

	/* Test retrieval */
	for (i = 0; init_data[i].name; i++) {
		v = dict_find (s, init_data[i].name);
		printf ("finding <'%s'> yields %x\n",
			init_data[i].name, v);
	}


	/* Test keys, values, array */
	{
		dict_key_t* retval;
		register i;

		retval = dict_keys (s);
		printf ("keys of 0x%x:\n", s);
		for (i = 0; retval[i] != 0; i++)
			printf (" >> %s\n", retval[i]);
		free (retval);
	}
	{
		dict_value_t* retval;
		register i;

		retval = dict_values (s);
		printf ("values of 0x%x:\n", s);
		for (i = 0; retval[i] != 0; i++)
			printf (" >> 0x%08x\n", retval[i]);
		free (retval);
	}

	/* Test deletion */
	for (i = 0; init_data[i].name; i++) {
		v = dict_remove (s, init_data[i].name);
		printf ("deleting <'%s'> yields %x\n",
			init_data[i].name, v);
	}
	dict_printOn(s, stdout);

	/* Test replacement */
	for (i = 0; init_data[i].name; i++) {
		static char* one = "one";
		static char* two = "two";

		v = dict_replace (s, init_data[i].name, one);
		if (v != 0) {
			printf ("initial replacement of '%s' non-zero!\n",
				init_data[i].name);
		}

		v = dict_replace (s, init_data[i].name, two);
		if (v != one) {
			printf ("2nd replacement of '%s' returns wrong value\n",
				init_data[i].name);
		}
	}
	printf ("After replace operations:\n");
	dict_printOn(s, stdout);
	s = dict_dispose(s);
}
#endif


syntax highlighted by Code2HTML, v. 0.9.1