/*
 *	reform: reformat file, esp. non-UNIX source. 0 of 4/24/77
 *	perform other manipulations needing meaning of tabs
 */
#define	NTABS	21	/* max # tab settings + 1 */
#define	NCOLS	512	/* max size of untabbed line */
#define	ESC	033

int tabin[NTABS],tabout[NTABS];
char	*tspec[2];		/* ptrs to tabspec1 & 2 */
#define TSPEC1	tspec[0]
#define TSPEC2	tspec[1]
int ntspec;		/* number of tabspecs found so far */

int	optb,	/* >0 ==> blank out 1st optb chars if blank/digits */
	opte,	/* >0 ==> extend length of each line to opte */
	optf,	/* >0 ==> write format line in front of file */
	opti 1,	/* # of interior blanks required to make tab */
	optm,	/* >0 ==> move left optm characters */
	optp,	/* >0 ==> prepend optp characters */
	opts,	/* >0 ==> SCCS R.L moved to end of line */
	optt;	/* >0 ==> truncate lines to optt chars */

/*
 *	main working buffer, ptr to first and last (nl) char,
 *	ptrs to rightmost and leftmost legal positions
 */
char	line[2*NCOLS+1];
char	*pfirst, *plast;
char	*liner &line[2*NCOLS],
	*linel &line[0];


#define LINESZ1	132
char	line1[LINESZ1];	/* buffer area for 1st line1 in file */
char	found;	/* flag to note existence of format spec */
char	srelev[8];	/* if +s, save SCCS SID: chars* tab */
int	savem;	/* will have m from file fspec */
struct buf {
	int fildes;	/* file descriptor */
	int nleft;	/* bytes left */
	char *nextp;	/* ptr to next char */
	char buffer[512];	/* i/o area */
} fin, fout;

static char SCCSID[] "@(#)reform.c	2.11";
main(argc,argv)
int argc; char **argv;
{
	get1line();	/* initialize input buffer with 1 line (at least) */
	options(argc,argv);
	fout.fildes = 1;
	if (optf)
		fwrite();	/* format spec into new file */
	while (getline(tabin) >= 0) {
		if (optp) prepend(optp);
		if (optt) truncate(optt);
		remtrail();
		if (opte) extend(opte);
		if (opts) sccsadd();
		if (optb) blankout(optb);
		if (optm) movleft(optm);
		contract(tabout,opti);
	}
	fflush(&fout);
	exit(0);
}

/*
 *	get1line: prime fin buffer with 1st line, for use with -- tabspecs.
 *	leaves fin so that getchal rereads 1st line
 */
get1line()
{
	register int i;
	register char *p;
	fin.nextp = p = &fin.buffer[0];
	for (i = 0; i <= 511;) {
		if (read(0, p, 1) != 1) break;
		i++;
		if (*p++ == '\n') break;
	}
	fin.nleft = i;
	return;
}

/*	options: scan option flags, setting flags opt? */
options(argc,argv)
int argc; char **argv;
{
	register char c;
	register int n;
	char *scan;
	while (--argc > 0) {
		scan = *++argv;		/* next arg, skipping 1st */
		if (*scan == '+') {
			c = *++scan;	/* option code */
			scan++;
			n = getnum(&scan);
			switch(c) {
			case 'b':
				optb = n?n:6; break;
			case 'e':
				opte = n?n:72; break;
			case 'f':
				optf++;
				break;
			case 'i':
				opti = n?n:2; break;
			case 'm':
				optm = n?n:6; break;
			case 'p':
				optp = n?n:6; break;
			case 's':
				opts++;
				if (!opte) opte = 72;
				break;
			case 't':
				optt = n?n:72; break;
			}
		}
		else if (ntspec < 2)
			tspec[ntspec++] = scan;	/* save tabspec */
	}
	if (!TSPEC1)
		TSPEC1 = "--";		/* 1st defaults to 1st line of file */
	if (!TSPEC2)
		TSPEC2 = TSPEC1;	/* but 2nd defaults to 1st tabspec */
	scantab(TSPEC1,tabin,0);
	if (opts)
		opte =- savem;
	scantab(TSPEC2,tabout,0);
	return;
}

/*
 *	functions scantab, repetab, arbitab, and getnum are identical to
 *	those in tabs(I).  filetab is similar, but saves 1st-line read
 *	and found flag externally, so that fwrite may use them
 */

/*	scantab: scan 1 tabspec & return tab list for it */

scantab(scan,tabvect,level)
char	*scan;
int tabvect[NTABS], level;
{
	register char c;
	if (*scan == '-')
		if ((c = *++scan) == '-')
			filetab(++scan,tabvect,level);
		else if (c >= '0' && c <= '9')
			repetab(scan,tabvect);
		else if (stdtab(scan,tabvect))
			err("unknown tab code");
		else;
	else
		arbitab(scan,tabvect);
	return;
}

/*	repetab: scan and set repetitive tabs, 1+n, 1+2*n, etc */

repetab(scan,tabvect)
char	*scan;
int tabvect[NTABS];
{
	register int incr, i, tabn;
	int limit;
	incr = getnum(&scan);
	tabn = 1;
	limit = (NCOLS - 1)/(incr?incr:1) - 1; /* # last actual tab */
	if (limit>NTABS-2)
		limit = NTABS-2;
	for (i = 0; i<=limit; i++)
		tabvect[i] = tabn =+ incr;
	tabvect[i] = 0;
	return;
}

/*	arbitab: handle list of arbitrary tabs */

arbitab(scan,tabvect)
char	*scan;
int tabvect[NTABS];
{
	register int i, t, last;
	char c;
	last = 0;
	for (i = 0; i<NTABS-1;) {
		if ((c = *scan) == '+') {
			scan++;		/* +n ==> increment, not absolute */
			if (t = getnum(&scan))
				tabvect[i++] = last =+ t;
			else err("illegal increment");
		}
		else {
			if ((t = getnum(&scan)) > last)
				tabvect[i++] = last = t;
			else err("illegal tabs");
		}
		if (*scan++ != ',') break;
	}
	if (last > NCOLS)
		err("illegal tabs");
	tabvect[i] = 0;
	return;
}

/*	filetab: copy tabspec from existing file */
filetab(scan,tabvect,level)
char	*scan;
int tabvect[NTABS];
{
	register int length, i;
	register char c;
	char *endspec;		/* ptr to blank or colon after tspec*/
	char savchar;
	int fildes;
	char state;
	char *temp;
	if (level)
		err("file indirection");
	if (*scan == '\0') {	/* tabspec was -- by itself, get from 1st line*/
		temp = &fin.buffer[0];
		if (opts) {
			while ((c = *temp++) != '\t' && c != '\n');
			if (c != '\t')
				err("not SCCS -m");
		}
		for (i = 0; i < LINESZ1;)
			line1[i++] = *temp++;	/* copy to safe place */
		length = LINESZ1;
	}
	else {
		if ((fildes = open(scan,0)) < 0)
			err("can't open");
		length = read(fildes,line1,LINESZ1);
		close(fildes);
	}
	found = state = 0;
	scan = 0;
	for (i = 0; i<length && (c = line1[i]) != '\n'; i++) {
		switch (state) {
		case 0:
			state = (c == '<'); break;
		case 1:
			state = (c == ':')?2:0; break;
		case 2:
		x:
			if (c == 't')
				state = 3;
			else if (c == ':')
				state = 6;
			else if (c != ' ') {
				if (c != 'm')
					state = 5;
				else {
					savem = line1[++i]-'0';
					c = line1[++i];
					goto x;
				}
			}
			break;
		case 3:
			if (c == ' ')
				state = 2;
			else {
				scan = &line1[i];
				if (*(scan+1) == 'c' && *(scan+2) == '3')
					savem = 6;
				state = 4;
			}
			break;
		case 4:
			if (c == ' ') {
				endspec = &line1[i];
				state = 5;
			}
			else if (c == ':') {
				endspec = &line1[i];
				state = 6;
			}
			break;
		case 5:
			if (c == 'm')
				goto x;
			if (c == ' ')
				state = 2;
			else if (c == ':')
				state = 6;
			break;
		case 6:
			if (c == '>') {
				found = 1;
				goto done;
			}
			else state = 5;
			break;
		}
	}
done:
	if (found && scan != 0) {
		savchar = *endspec;
				*endspec ='\0';
		scantab(scan,tabvect,1);
		*endspec = savchar;
	}
	else scantab("-8",tabvect,1);
	return;
}

/*
 *	fwrite: write format specification (tabout) as 1st line of file
 *	entry: if tabspec from --file, expects line1 and found to be set
 */

fwrite()
{
	if(TSPEC2[0] == '-' && TSPEC2[1] == '-')
		if (found)
			output(line1);
		else
			output("<:t-8 d:>\n");
	else {
		output("<:t");
		output(TSPEC2);
		output(" d:>\n");
	}
	return;
}

/*	getline: read next line into line, expand tabs into blanks */
/*	exit: return -1 on EOF, >= 0 otherwise */

getline(itab)
int itab[];
{
	char c;
	int i;		/* counter for sccs prefix */
	int ctab, pctab;
	register int nextcol;	/* logical position (allow BS/CTRL,etc*/
	register char *p;
	if (opts) {		/* if SCCS, stash SID away */
		for(i = 0; i <= 7; i++)
			srelev[i] = ' ';
		i = 0;
		while ((c = getchal()) != '\t') {
			if (c == -1)
				return(-1);	/* EOF or error */
			if (c == '\n')
				err("not SCCS -m");
			if (i <= 7)
				srelev[i++] = c;
			else
				srelev[7] = '*';	/* overflow, mark */
		}
	}
	nextcol = 1;
	p = pfirst = line+NCOLS;	/* start at middle of workarea */
	do {
		if ((c = getchal()) < 0)
			break;
		if (c == '\t') {
			while ((ctab = *itab) && nextcol >= ctab) itab++;
			pctab = ctab ? p + (ctab-nextcol) : p+1;
			nextcol =+ pctab-p;	/* adjust column for tab */
			while(p < pctab) *p++ = ' ';
		}
		else if ((*p++ = c) == '\n') break;
		if (c >= ' ') nextcol++;	/* all normal chars */
		else if (c == '\b' || c == ESC) nextcol--;
		/*	ESC+anything or ctrl char = 0-width */
	} while (p <= liner);
	plast = p-1;
	if (p > liner)
		err("line too long");
	return(c);
}

/*
 *	getchal: local variant of getchar with stop mode processing:
 *	make sure output flushed if last char was (nl), thus allowing
 *	nroff -s or .rd requests with small overhead.
 *	note EOF return is -1 rather than 0
 */
getchal()
{
	if (fin.nleft <= 0) {
		if (fin.nextp != 0 && *--fin.nextp == '\n')
			fflush(&fout);
		fin.nleft = read(fin.fildes,fin.buffer,512);
		fin.nextp = fin.buffer;
	}
	if (fin.nleft-- > 0) return(*fin.nextp++);
	return(-1);
}

/*	prepend: add blanks to front of line */
prepend(n)
register int	n;
{
	register char *p;
	p = pfirst;
	if (pfirst < linel+n)
		err("line too long");
	while(n--) *--p = ' ';
	pfirst = p;
	return;
}

/*	truncate: truncate line to n chars max */
truncate(n)
register int	n;
{
	if (plast > pfirst+n)
		*(plast = pfirst+n) = '\n';
	return;
}

/*	remtrail: delete trailing blanks */
remtrail()
{
	register char *p;
	p = plast;
	while(*--p == ' ' && p >= pfirst);
	*++p = '\n';
	plast = p;
	return;
}

/*	blankout: blank first n chars, iff they are blanks/digits */
blankout(n)
register int	n;
{
	register char *p, *ptemp;
	char doblank;
	doblank = 1;
	ptemp = ((p = pfirst+n-1)<plast)?p:plast-1;
	for (p = pfirst; p <= ptemp && doblank; p++)
		if ((*p < '0' || *p > '9') && *p != ' ') doblank = 0;
	if (doblank)
		for(p = pfirst; p <= ptemp;) *p++ = ' ';
	return;
}

/*	extend: extend lines to fixed length */
extend(n)
register int	n;
{
	register char *p;
	register char *ptemp;
	p = plast;
	plast = ((ptemp = pfirst+n) < liner)?ptemp:liner-1;
	ptemp = plast;
	while (p < ptemp) *p++ = ' ';
	*p = '\n';
	return;
}

/*	movleft: shift line left (remove leading chars) */
movleft(n)
register int	n;
{
	pfirst = ((pfirst =+ n)<plast)?pfirst:plast;
	return;
}

/*
 *	sccsadd: add SCCS SID (release.level[etc...] onto end of line)
 */
sccsadd()
{
	register char *p;
	register int i;
	p = plast;		/* current loc of '\n' */
	for (i = 0; i <= 7; i++)
		if (srelev[i] != ' ')
			*p++ = srelev[i];
	*p = '\n';
	plast = p;
	return;
}

/*	contract: compress blanks into tabs and write output */
contract(itab,n)
int itab[], n;
{
	char outbuf[NCOLS];
	register char *p;	/* ptr to next char of input */
	register char *pout;	/* ptr to next empty slot of outbuf */
	register char c;
	int blnkcnt;	/* count of length of contiguous blank string */
	int nextcol;	/* next column # to be filled */
	int t, it;
	p = pfirst;
	pout = outbuf;
	blnkcnt = 0;
	nextcol = 1;
	do {
		while ((c = *p++) == ' ')
			blnkcnt++;
		if (blnkcnt) {	/* 1st nonblank after blank string */
			while (*itab && nextcol >= *itab) itab++;
			if (blnkcnt >= n || nextcol == 1) /* always lead blnks */
				while((it = *itab) && it <= nextcol+blnkcnt) {
					*pout++ = '\t';
					t = it-nextcol;
					blnkcnt =- t;
					nextcol =+ t;
					itab++;
				}
		}
		nextcol =+ blnkcnt;	/* leftover blanks, if any */
		while(blnkcnt--)
			*pout++ = ' ';
		blnkcnt = 0;
		*pout++ = c;	/* finally, actual input char */
		if (c >= ' ') nextcol++;	/* normal chars */
		else if (c == '\b' || c == ESC)
			nextcol--;
		/* ESC-anything or other ctrl chars = 0-width */
	} while(c != '\n');
	output(outbuf);
	return;
}

/*	output: write 1 line (terminated by '\0' or '\n') */
output(str)
register char	*str;
{
	register char c;
	while (c = *str++) {
		putc(c,&fout);
		if (c == '\n') break;
	}
	return;
}

/*	err: terminate with error and print diagnostic */
err(mesg)
register char	*mesg;
{
	int ttydes,ttyold[3];
	char *p;
	if (gtty(ttydes = 2,ttyold)) ttydes = 1;
	write(ttydes,"reform:",7);
	p = mesg;
	while(*p++);
	write(ttydes,mesg,p-mesg);
	write(ttydes,"\n",1);
	exit(1);
}

/*
 *	getnum: scan and convert number, return zero if none found
 *	set scan ptr to addr of ending delimiter
 */
getnum(scan1)
char	**scan1;
{
	register int n;
	register char c, *scan;
	n = 0;
	scan = *scan1;
	while ((c = *scan++) >= '0' && c <= '9') n = n * 10 + c -'0';
	*scan1 = --scan;
	return(n);
}

/*
 *	stdtabs: standard tabs table
 *	format: option code letter(s), null, tabs, null
 */
char	stdtabs[] {
'a',	0,1,10,16,36,72,0,			/* IBM 370 Assembler */
'a','2',0,1,10,16,40,72,0,			/* IBM Assembler alternative*/
'c',	0,1,8,12,16,20,55,0,			/* COBOL, normal */
'c','2',0,1,6,10,14,49,0,			/* COBOL, crunched*/
'c','3',0,1,6,10,14,18,22,26,30,34,38,42,46,50,54,58,62,67,0,
						/* crunched COBOL, many tabs */
'f',	0,1,7,11,15,19,23,0,			/* FORTRAN */
'p',	0,1,5,9,13,17,21,25,29,33,37,41,45,49,53,57,61,0, /* PL/I */
's',	0,1,10,55,0,				/* SNOBOL */
'u',	0,1,12,20,44,0,				/* UNIVAC ASM */
0};

/*
 *	stdtab: return tab list for any "canned" tab option.
 *	entry: option points to null-terminated option string
 *		tabvect points to vector to be filled in
 *	exit: return(0) if legal, tabvect filled, ending with zero
 *		return(-1) if unknown option
 */
stdtab(option,tabvect)
char	option[];
int tabvect[];
{
	register char *sp;
	tabvect[0] = 0;
	sp = stdtabs;
	while (*sp) {
		if (equal(option,sp)) {
			while (*sp++);		/* skip to 1st tab value */
			while (*tabvect++ = *sp++);	/* copy, make int */
			return(0);
		}
		while(*sp++);	/* skip to 1st tab value */
		while(*sp++);		/* skip over tab list */
	}
	return(-1);
}


/*	equal: string comparison, return 1 if equal, 0 otherwise */
equal(s1,s2)
char	*s1,*s2;
{
	register char c;
	while((c = *s1++) == *s2++ && c);
	if (c == *--s2)
		return(1);
	return(0);
}
