# include "lex.h"
# include "macro.h"

/*
 * The macro handling routines. These use the hash table.
 */

# define MCHUNKSZ	32

typedef struct _mchunk_ {
    struct _mchunk_ *next;	/* next in list */
    macro m[MCHUNKSZ];		/* macros */
} mchunk;

static mchunk *mlist;		/* list of macro chunks */
static int mchunksz;		/* size of current macro chunk */
static macro *flist;		/* list of free macros */

static hashtab *mt;		/* macro hash table */

/*
 * NAME:	macro->init()
 * DESCRIPTION:	intiialize the macro table
 */
void mc_init()
{
    mt = ht_new(MACTABSZ, MACHASHSZ);
    mlist = (mchunk *) NULL;
    mchunksz = MCHUNKSZ;
    flist = (macro *) NULL;
}

/*
 * NAME:	macro->clear()
 * DESCRIPTION:	clear the macro table
 */
void mc_clear()
{
    register macro *m;
    register mchunk *l, *f;

    if (mt != (hashtab *) NULL) {
	ht_del(mt);
	mt = (hashtab *) NULL;

	for (l = mlist; l != (mchunk *) NULL; ) {
	    for (m = l->m; mchunksz > 0; m++, --mchunksz) {
		if (m->chain.name != (char *) NULL) {
		    FREE(m->chain.name);
		    if (m->replace != (char *) NULL) {
			FREE(m->replace);
		    }
		}
	    }
	    mchunksz = MCHUNKSZ;
	    f = l;
	    l = l->next;
	    FREE(f);
	}
	mlist = (mchunk *) NULL;
    }
}

/*
 * NAME:	macro->define()
 * DESCRIPTION:	define a macro
 */
void mc_define(name, replace, narg)
register char *name, *replace;
int narg;
{
    register macro **m;

    m = (macro **) ht_lookup(mt, name, FALSE);
    if (*m != (macro *) NULL) {
	/* the macro already exists. */
	if ((*m)->replace != (char *) NULL &&
	    ((*m)->narg != narg || strcmp((*m)->replace, replace) != 0)) {
	    warning("macro %s redefined", name);
	}
    } else {
	if (flist != (macro *) NULL) {
	    /* get macro from free list */
	    *m = flist;
	    flist = (macro *) flist->chain.next;
	} else {
	    /* allocate new macro */
	    if (mchunksz == MCHUNKSZ) {
		register mchunk *l;

		l = ALLOC(mchunk, 1);
		l->next = mlist;
		mlist = l;
		mchunksz = 0;
	    }
	    *m = &mlist->m[mchunksz++];
	}
	(*m)->chain.next = (hte *) NULL;
	(*m)->chain.name = strcpy(ALLOC(char, strlen(name) + 1), name);
	(*m)->replace = (char *) NULL;
    }
    /* fill in macro */
    if (replace != (char *) NULL) {
	(*m)->replace = strcpy(REALLOC((*m)->replace, char, 0,
				       strlen(replace) + 1),
			       replace);
    } else {
	(*m)->replace = (char *) NULL;
    }
    (*m)->narg = narg;
}

/*
 * NAME:	macro->undef()
 * DESCRIPTION:	undefine a macro
 */
void mc_undef(name)
char *name;
{
    register macro **m, *mac;

    m = (macro **) ht_lookup(mt, name, FALSE);
    if (*m != (macro *) NULL) {
	/* it really exists. */
	mac = *m;
	FREE(mac->chain.name);
	mac->chain.name = (char *) NULL;
	if (mac->replace != (char *) NULL) {
	    FREE(mac->replace);
	    mac->replace = (char *) NULL;
	}
	*m = (macro *) mac->chain.next;
	/* put macro in free list */
	mac->chain.next = (hte *) flist;
	flist = mac;
    }
}

/*
 * NAME:	macro->lookup()
 * DESCRIPTION:	lookup a macro definition in the macro table. Return &NULL if
 *		the macro is not found.
 */
macro *mc_lookup(name)
char *name;
{
    return *(macro **) ht_lookup(mt, name, TRUE);
}
