/*
 * troff3.c
 * 
 * macro and string routines, storage allocation
 */

#include "tdef.h"
#include "fns.h"
#include "ext.h"

Tchar	*argtop;
int	pagech = '%';
int	strflg;

#define	MHASHSIZE	128	/* must be 2**n */
#define	MHASH(x)	((x>>6)^x) & (MHASHSIZE-1)
Contab	*mhash[MHASHSIZE];


Blockp	*blist;		/* allocated blocks for macros and strings */
int	nblist;		/* how many there are */
int	bfree = -1;	/* first (possible) free block in the list */

Contab *contabp = NULL;
#define MDELTA 500
int	nm = 0;

int savname;		/* name of macro/string being defined */
int savslot;		/* place in Contab of savname */
int freeslot = -1;	/* first (possible) free slot in contab */

void prcontab(Contab *p)
{
	int i;
	for (i = 0; i < nm; i++)
		if (p)
			if (p[i].rq != 0)
				fprintf(stderr, "slot %d, %-2.2s\n", i, unpair(p[i].rq));
			else
				fprintf(stderr, "slot %d empty\n", i);
		else
			fprintf(stderr, "slot %d empty\n", i);
}


void blockinit(void)
{
	blist = (Blockp *) calloc(NBLIST, sizeof(Blockp));
	if (blist == NULL) {
		ERROR "not enough room for %d blocks", NBLIST WARN;
		done2(1);
	}
	nblist = NBLIST;
	blist[0].nextoff = blist[1].nextoff = -1;
	blist[0].bp = (Tchar *) calloc(BLK, sizeof(Tchar));
	blist[1].bp = (Tchar *) calloc(BLK, sizeof(Tchar));
		/* -1 prevents blist[0] from being used; temporary fix */
		/* for a design botch: offset==0 is overloaded. */
		/* blist[1] reserved for .rd indicator -- also unused. */
		/* but someone unwittingly looks at these, so allocate something */
	bfree = 2;
}


char *grow(char *ptr, int num, int size)	/* make array bigger */
{
	char *p;

	if (ptr == NULL)
		p = (char *) calloc(num, size);
	else
		p = (char *) realloc(ptr, num * size);
	return p;
}

void mnspace(void)
{
	nm = sizeof(contab)/sizeof(Contab) + MDELTA;
	freeslot = sizeof(contab)/sizeof(Contab) + 1;
	contabp = (Contab *) grow((char *) contabp, nm, sizeof(Contab));
	if (contabp == NULL) {
		ERROR "not enough memory for namespace of %d marcos", nm WARN;
		exit(1);
	}
	contabp = (Contab *) memcpy((char *) contabp, (char *)contab,
							sizeof(contab));
	if (contabp == NULL) {
		ERROR "Cannot reinitialize macro/request name list" WARN;
		exit(1);
	}

}

void caseig(void)
{
	int i;
	Offset oldoff = offset;

	offset = 0;
	i = copyb();
	offset = oldoff;
	if (i != '.')
		control(i, 1);
}


void casern(void)
{
	int i, j, k;

	lgf++;
	skip();
	if ((i = getrq()) == 0 || (oldmn = findmn(i)) < 0)
		return;
	skip();
	clrmn(findmn(j = getrq()));
	if (j) {
		munhash(&contabp[oldmn]);
		contabp[oldmn].rq = j;
		maddhash(&contabp[oldmn]);
		if (dip != d )
			for (k = dilev; k; k--)
				if (d[k].curd == i)
					d[k].curd = j;
	}
}

void maddhash(Contab *rp)
{
	Contab **hp;

	if (rp->rq == 0)
		return;
	hp = &mhash[MHASH(rp->rq)];
	rp->link = *hp;
	*hp = rp;
}

void munhash(Contab *mp)
{	
	Contab *p;
	Contab **lp;

	if (mp->rq == 0)
		return;
	lp = &mhash[MHASH(mp->rq)];
	p = *lp;
	while (p) {
		if (p == mp) {
			*lp = p->link;
			p->link = 0;
			return;
		}
		lp = &p->link;
		p = p->link;
	}
}

void mrehash(void)
{
	Contab *p;
	int i;

	for (i=0; i < MHASHSIZE; i++)
		mhash[i] = 0;
	for (p=contabp; p < &contabp[nm]; p++)
		p->link = 0;
	for (p=contabp; p < &contabp[nm]; p++) {
		if (p->rq == 0)
			continue;
		i = MHASH(p->rq);
		p->link = mhash[i];
		mhash[i] = p;
	}
}

void caserm(void)
{
	int j, k;

	lgf++;
g0:
	while (!skip() && (j = getrq()) != 0) {
		if (dip != d)
			for (k = dilev; k; k--)
				if (d[k].curd == j) {
					ERROR "cannot remove diversion %s during definition",
								unpair(j) WARN;
					goto g0;
				}
		clrmn(findmn(j));
	}
	lgf--;
}


void caseas(void)
{
	app++;
	caseds();
}


void caseds(void)
{
	ds++;
	casede();
}


void caseam(void)
{
	app++;
	casede();
}


void casede(void)
{
	int i, req;
	Offset savoff;

	req = '.';
	lgf++;
	skip();
	if ((i = getrq()) == 0)
		goto de1;
	if ((offset = finds(i)) == 0)
		goto de1;
	if (newmn)
		savslot = newmn;
	else
		savslot = findmn(i);
	savname = i;
	if (ds)
		copys();
	else
		req = copyb();
	clrmn(oldmn);
	if (newmn) {
		if (contabp[newmn].rq)
			munhash(&contabp[newmn]);
		contabp[newmn].rq = i;
		maddhash(&contabp[newmn]);

	}
	if (apptr) {
		savoff = offset;
		offset = apptr;
		wbf((Tchar) IMP);
		offset = savoff;	/* pointless */
		USED(offset);
	}
	offset = dip->op;
	if (req != '.')
		control(req, 1);
de1:
	ds = app = 0;
}


int findmn(int i)
{
	Contab *p;

	for (p = mhash[MHASH(i)]; p; p = p->link)
		if (i == p->rq)
			return(p - contabp);
	return(-1);
}


void clrmn(int i)
{
	if (i >= 0) {
		if (contabp[i].mx)
			ffree(contabp[i].mx);
		munhash(&contabp[i]);
		contabp[i].rq = 0;
		contabp[i].mx = 0;
		contabp[i].emx = 0;
		contabp[i].f = 0;
		if (contabp[i].divsiz != NULL) {
			free(contabp[i].divsiz);
			contabp[i].divsiz = NULL;
		}
		if (freeslot > i)
			freeslot = i;
	}
}

void growcontab(void)
{
	nm += MDELTA;
	contabp = (Contab *) grow((char *) contabp , nm, sizeof(Contab));
	if (contabp == NULL) {
		ERROR "Too many (%d) string/macro names", nm WARN;
		done2(02);
	} else {
		memset((char *)(contabp) + (nm - MDELTA) * sizeof(Contab),
						0, MDELTA * sizeof(Contab));
		mrehash();
	}
}


Offset finds(int mn)
{
	int i;
	Offset savip;

	oldmn = findmn(mn);
	newmn = 0;
	apptr = 0;
	if (app && oldmn >= 0 && contabp[oldmn].mx) {
		savip = ip;
		ip = contabp[oldmn].emx;
		oldmn = -1;
		apptr = ip;
		if (!diflg)
			ip = incoff(ip);
		nextb = ip;
		ip = savip;
	} else {
		for (i = freeslot; i < nm; i++) {
			if (contabp[i].rq == 0)
				break;
		}
		if (i == nm) 
			growcontab();
		freeslot = i + 1;
		if ((nextb = alloc()) == -1) {
			app = 0;
			if (macerr++ > 1)
				done2(02);
			if (nextb == 0)
				ERROR "Not enough space for string/macro names" WARN;
			edone(04);
			return(offset = 0);
		}
		contabp[i].mx = nextb;
		if (!diflg) {
			newmn = i;
			if (oldmn == -1)
				contabp[i].rq = -1;
		} else {
			contabp[i].rq = mn;
			maddhash(&contabp[i]);
		}
	}
	app = 0;
	return(offset = nextb);
}

int skip(void)
{
	Tchar i;

	while (cbits(i = getch()) == ' ' || ismot(i))
		;
	ch = i;
	return(nlflg);
}


int copyb(void)
{
	int i, j, state;
	Tchar ii;
	int req, k;
	Offset savoff;
	Uchar *p;

	if (skip() || !(j = getrq()))
		j = '.';
	req = j;
	p = unpair(j);
	/* was: k = j >> BYTE; j &= BYTEMASK; */
	j = p[0];
	k = p[1];
	copyf++;
	flushi();
	nlflg = 0;
	state = 1;
	savoff = 0;

/* state 0	eat up
 * state 1	look for .
 * state 2	look for first char of end macro
 * state 3	look for second char of end macro
 */

	while (1) {
		i = cbits(ii = getch());
		if (state == 3) {
			if (i == k)
				break;
			if (!k) {
				ch = ii;
				i = getach();
				ch = ii;
				if (!i)
					break;
			}
			state = 0;
			goto c0;
		}
		if (i == '\n') {
			state = 1;
			nlflg = 0;
			goto c0;
		}
		if (state == 1 && i == '.') {
			state++;
			savoff = offset;
			goto c0;
		}
		if (state == 2 && i == j) {
			state++;
			goto c0;
		}
		state = 0;
c0:
		if (offset)
			wbf(ii);
	}
	if (offset) {
		offset = savoff;
		wbf((Tchar)0);
	}
	copyf--;
	return(req);
}


void copys(void)
{
	Tchar i;

	copyf++;
	if (skip())
		goto c0;
	if (cbits(i = getch()) != '"')
		wbf(i);
	while (cbits(i = getch()) != '\n')
		wbf(i);
c0:
	wbf((Tchar)0);
	copyf--;
}


Offset alloc(void)	/* return free Offset in nextb */
{
	int i, j;

	for (i = bfree; i < nblist; i++)
		if (blist[i].nextoff == 0)
			break;
	if (i == nblist) {
		blist = (Blockp *) realloc((char *) blist, 2 * nblist * sizeof(Blockp));
		if (blist == NULL) {
			ERROR "can't grow blist for string/macro defns" WARN;
			done2(2);
		}
		nblist *= 2;
		for (j = i; j < nblist; j++) {
			blist[j].nextoff = 0;
			blist[j].bp = 0;
		}
	}
	blist[i].nextoff = -1;	/* this block is the end */
	bfree = i + 1;
	if (blist[i].bp == 0)
		blist[i].bp = (Tchar *) calloc(BLK, sizeof(Tchar));
	if (blist[i].bp == NULL) {
		ERROR "can't allocate memory for string/macro definitions" WARN;
		done2(2);
	}
	nextb = (Offset) i * BLK;
	return nextb;
}


void ffree(Offset i)	/* free list of blocks starting at blist(o) */
{			/* (doesn't actually free the blocks, just the pointers) */
	int j;

	for ( ; blist[j = bindex(i)].nextoff != -1; ) {
		if (bfree > j)
			bfree = j;
		i = blist[j].nextoff;
		blist[j].nextoff = 0;
	}
	blist[j].nextoff = 0;
}


void wbf(Tchar i)	/* store i into offset, get ready for next one */
{
	int j, off;

	if (!offset)
		return;
	j = bindex(offset);
	if (i == 0)
		contabp[savslot].emx = offset;
	off = boffset(offset);
	blist[j].bp[off] = i;
	offset++;
	if (pastend(offset)) {	/* off the end of this block */
		if (blist[j].nextoff == -1) {
			if ((nextb = alloc()) == -1) {
				ERROR "Out of temp file space" WARN;
				done2(01);
			}
			blist[j].nextoff = nextb;
		}
		offset = blist[j].nextoff;
	}
}


Tchar rbf(void)	/* return next char from blist[] block */
{
	Tchar i, j;

	if (ip == RD_OFFSET) {		/* for rdtty */
		if (j = rdtty())
			return(j);
		else
			return(popi());
	}
	
	i = rbf0(ip);
	if (i == 0) {
		if (!app)
			i = popi();
		return(i);
	}
	ip = incoff(ip);
	return(i);
}


Offset xxxincoff(Offset p)		/* get next blist[] block */
{
	p++;
	if (pastend(p)) {		/* off the end of this block */
		if ((p = blist[bindex(p-1)].nextoff) == -1) {	/* and nothing was allocated after it */
			ERROR "Bad storage allocation" WARN;
			done2(-5);
		}
	}
	return(p);
}


Tchar popi(void)
{
	Stack *p;

	if (frame == stk)
		return(0);
	if (strflg)
		strflg--;
	p = nxf = frame;
	p->nargs = 0;
	frame = p->pframe;
	ip = p->pip;
	pendt = p->ppendt;
	lastpbp = p->lastpbp;
	return(p->pch);
}

/*
 *	test that the end of the allocation is above a certain location
 *	in memory
 */
#define SPACETEST(base, size) \
	if ((char*)base + size >= (char*)stk+STACKSIZE) \
		ERROR "Stacksize overflow in n3" WARN

Offset pushi(Offset newip, int  mname)
{
	Stack *p;

	SPACETEST(nxf, sizeof(Stack));
	p = nxf;
	p->pframe = frame;
	p->pip = ip;
	p->ppendt = pendt;
	p->pch = ch;
	p->lastpbp = lastpbp;
	p->mname = mname;
	lastpbp = pbp;
	pendt = ch = 0;
	frame = nxf;
	if (nxf->nargs == 0) 
		nxf += 1;
	else 
		nxf = (Stack *)argtop;
	return(ip = newip);
}


void *setbrk(int x)
{
	char *i;

	if ((i = (char *) calloc(x, 1)) == 0) {
		ERROR "Core limit reached" WARN;
		edone(0100);
	}
	return(i);
}


int getsn(void)
{
	int i;

	if ((i = getach()) == 0)
		return(0);
	if (i == '(')
		return(getrq());
	else 
		return(i);
}


Offset setstr(void)
{
	int i, j;

	lgf++;
	if ((i = getsn()) == 0 || (j = findmn(i)) == -1 || !contabp[j].mx) {
		lgf--;
		return(0);
	} else {
		SPACETEST(nxf, sizeof(Stack));
		nxf->nargs = 0;
		strflg++;
		lgf--;
		return pushi(contabp[j].mx, i);
	}
}



void collect(void)
{
	int j;
	Tchar i, *strp, *lim, **argpp, **argppend;
	int quote;
	Stack *savnxf;

	copyf++;
	nxf->nargs = 0;
	savnxf = nxf;
	if (skip())
		goto rtn;

	{
		char *memp;
		memp = (char *)savnxf;
		/*
		 *	1 s structure for the macro descriptor
		 *	APERMAC Tchar *'s for pointers into the strings
		 *	space for the Tchar's themselves
		 */
		memp += sizeof(Stack);
		/*
		 *	CPERMAC = the total # of characters for ALL arguments
		 */
#define	CPERMAC	200
#define	APERMAC	9
		memp += APERMAC * sizeof(Tchar *);
		memp += CPERMAC * sizeof(Tchar);
		nxf = (Stack *)memp;
	}
	lim = (Tchar *)nxf;
	argpp = (Tchar **)(savnxf + 1);
	argppend = &argpp[APERMAC];
	SPACETEST(argppend, sizeof(Tchar *));
	strp = (Tchar *)argppend;
	/*
	 *	Zero out all the string pointers before filling them in.
	 */
	for (j = 0; j < APERMAC; j++)
		argpp[j] = 0;
	/* ERROR "savnxf=0x%x,nxf=0x%x,argpp=0x%x,strp=argppend=0x%x, lim=0x%x",
	 * 	savnxf, nxf, argpp, strp, lim WARN;
	 */
	strflg = 0;
	while (argpp != argppend && !skip()) {
		*argpp++ = strp;
		quote = 0;
		if (cbits(i = getch()) == '"')
			quote++;
		else 
			ch = i;
		while (1) {
			i = getch();
/* fprintf(stderr, "collect %c %d\n", cbits(i), cbits(i)); */
			if (nlflg || (!quote && argpp != argppend && cbits(i) == ' '))
				break;	/* collects rest into $9 */
			if (   quote
			    && cbits(i) == '"'
			    && cbits(i = getch()) != '"') {
				ch = i;
				break;
			}
			*strp++ = i;
			if (strflg && strp >= lim) {
				/* ERROR "strp=0x%x, lim = 0x%x", strp, lim WARN; */
				ERROR "Macro argument too long" WARN;
				copyf--;
				edone(004);
			}
			SPACETEST(strp, 3 * sizeof(Tchar));
		}
		*strp++ = 0;
	}
	nxf = savnxf;
	nxf->nargs = argpp - (Tchar **)(savnxf + 1);
	argtop = strp;
rtn:
	copyf--;
}


void seta(void)
{
	int i;

	i = cbits(getch()) - '0';
	if (i > 0 && i <= APERMAC && i <= frame->nargs)
		pushback(*(((Tchar **)(frame + 1)) + i - 1));
}


void caseda(void)
{
	app++;
	casedi();
}

void casegd(void)
{
	int i, j;

	skip();
	if ((i = getrq()) == 0)
		return;
	if ((j = findmn(i)) >= 0) {
		if (contabp[j].divsiz != NULL) {
			numtabp[DN].val = contabp[j].divsiz->dix;
			numtabp[DL].val = contabp[j].divsiz->diy;
		}
	}
}

#define FINDDIV(o) if ((o =  findmn(dip->curd)) < 0) \
			ERROR "lost diversion %s", unpair(dip->curd) WARN

void casedi(void)
{
	int i, j, *k;

	lgf++;
	if (skip() || (i = getrq()) == 0) {
		if (dip != d) {
			FINDDIV(savslot);
			wbf((Tchar)0);
		}
		if (dilev > 0) {
			numtabp[DN].val = dip->dnl;
			numtabp[DL].val = dip->maxl;
			FINDDIV(j);
			if ((contabp[j].divsiz = (Divsiz *) malloc(sizeof(Divsiz))) == NULL) {
				ERROR "Cannot alloc diversion size" WARN;
				done2(1);
			} else {
				contabp[j].divsiz->dix = numtabp[DN].val;
				contabp[j].divsiz->diy = numtabp[DL].val;
			}
			dip = &d[--dilev];
			offset = dip->op;
		}
		goto rtn;
	}
	if (++dilev == NDI) {
		--dilev;
		ERROR "Diversions nested too deep" WARN;
		edone(02);
	}
	if (dip != d) {
		FINDDIV(j);
		savslot = j;
		wbf((Tchar)0);
	}
	diflg++;
	dip = &d[dilev];
	dip->op = finds(i);
	dip->curd = i;
	clrmn(oldmn);
	k = (int *) & dip->dnl;
	for (j = 0; j < 10; j++)
		k[j] = 0;	/*not op and curd*/
rtn:
	app = 0;
	diflg = 0;
}


void casedt(void)
{
	lgf++;
	dip->dimac = dip->ditrap = dip->ditf = 0;
	skip();
	dip->ditrap = vnumb((int *)0);
	if (nonumb)
		return;
	skip();
	dip->dimac = getrq();
}

void casetl(void)
{
	int j;
	int w[3];
	Tchar buf[LNSIZE];
	Tchar *tp;
	Tchar i, delim;

 	/*
 	 * bug fix
 	 *
 	 * if .tl is the first thing in the file, the p1
 	 * doesn't come out, also the pagenumber will be 0
 	 *
 	 * tends too confuse the device filter (and the user as well)
 	 */
 	if (dip == d && numtabp[NL].val == -1)
 		newline(1);
	dip->nls = 0;
	skip();
	if (ismot(delim = getch())) {
		ch = delim;
		delim = '\'';
	} else 
		delim = cbits(delim);
	tp = buf;
	numtabp[HP].val = 0;
	w[0] = w[1] = w[2] = 0;
	j = 0;
	while (cbits(i = getch()) != '\n') {
		if (cbits(i) == cbits(delim)) {
			if (j < 3)
				w[j] = numtabp[HP].val;
			numtabp[HP].val = 0;
			if (w[j] != 0)
				*tp++ = WORDSP;
			j++;
			*tp++ = 0;
		} else {
			if (cbits(i) == pagech) {
				setn1(numtabp[PN].val, numtabp[findr('%')].fmt,
				      i&SFMASK);
				continue;
			}
			numtabp[HP].val += width(i);
			if (tp < &buf[LNSIZE-10]) {
				if (cbits(i) == ' ' && *tp != WORDSP)
					*tp++ = WORDSP;
				*tp++ = i;
			} else {
				ERROR "Overflow in casetl" WARN;
			}
		}
	}
	if (j<3)
		w[j] = numtabp[HP].val;
	*tp++ = 0;
	*tp++ = 0;
	*tp = 0;
	tp = buf;
	if (NROFF)
		horiz(po);
	while (i = *tp++)
		pchar(i);
	if (w[1] || w[2])
		horiz(j = quant((lt - w[1]) / 2 - w[0], HOR));
	while (i = *tp++)
		pchar(i);
	if (w[2]) {
		horiz(lt - w[0] - w[1] - w[2] - j);
		while (i = *tp++)
			pchar(i);
	}
	newline(0);
	if (dip != d) {
		if (dip->dnl > dip->hnl)
			dip->hnl = dip->dnl;
	} else {
		if (numtabp[NL].val > dip->hnl)
			dip->hnl = numtabp[NL].val;
	}
}


void casepc(void)
{
	pagech = chget(IMP);
}


void casepm(void)
{
	int i, k;
	int xx, cnt, tcnt, kk, tot;
	Offset j;

	kk = cnt = tcnt = 0;
	tot = !skip();
	stackdump();
	for (i = 0; i < nm; i++) {
		if ((xx = contabp[i].rq) == 0 || contabp[i].mx == 0)
			continue;
		tcnt++;
		j = contabp[i].mx;
		for (k = 1; (j = blist[bindex(j)].nextoff) != -1; )
			k++; 
		cnt++;
		kk += k;
		if (!tot)
			fprintf(stderr, "%-2.2s %d\n", unpair(xx), k);
	}
	fprintf(stderr, "pm: total %d, macros %d, space %d\n", tcnt, cnt, kk);
}

void stackdump(void)	/* dumps stack of macros in process */
{
	Stack *p;

	if (frame != stk) {
		fprintf(stderr, "stack: ");
		for (p = frame; p != stk; p = p->pframe)
			fprintf(stderr, "%s ", unpair(p->mname));
		fprintf(stderr, "\n");
	}
}
