#
/*	reform: 2.5 of 2/13/76: reformat file (esp non-UNIX source code)*/
/*	NTABS = max # tab settings + 1 */
/*	NCOLS = max size of untabbed line */
#define NTABS 21
#define NCOLS 512
#define ESC 033

int tabin[NTABS],tabout[NTABS];
char *tspec[2];		/* ptrs to tabspec1 & 2 */
/*	following used to agree with manual page description */
#define TSPEC1	tspec[0]
#define TSPEC2	tspec[1]
int ntspec;		/* number of tabspecs found so far */
int maxtab NCOLS;

/* option flags */
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 */

char line[2*NCOLS+1];	/* line working buffer */
char *pfirst, *plast;	/* ptr to 1st, last (nl) chars of line */
char *liner, *linel;		/* rightmost legal position */
				/* leftmost legal position */

char srelev[10];	/* if +s, save SCCS R.L: RRRR.LLLLtab */

#define CARDSIZ	132
char card[CARDSIZ];	/* buffer area for 1st card in file */
char found;	/* flag to note existence of format spec */
struct buf {
	int fildes;	/* file descriptor */
	int nleft;	/* bytes left */
	char *nextp;	/* ptr to next char */
	char buffer[512];	/* i/o area */
} fin, fout;

char reform__[]	"~|^`s.reform.c	2.5";
main(argc,argv)
int argc; char **argv;
{
	liner = line + 2*NCOLS;
	linel = line;	/* init of linel and liner */
	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) */
get1line()
/*	leaves fin so that line is effectively reread by getchal */
{
	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);
	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))
			abend("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 = (maxtab-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 abend("illegal increment");
		}
		else {
			if ((t = getnum(&scan)) > last)
				tabvect[i++] = last = t;
			else abend("illegal tabs");
		}
		if (*scan++ != ',') break;
	}
	if (last > NCOLS)
		abend("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)
		abend("file indirection");
	if (*scan == '\0') {	/* tabspec was -- by itself, get from 1st line*/
		temp = &fin.buffer[0];
		if (opts)
			temp =+ 10;	/* ignore 1st 10 chars: SCCS R.L */
		for (i = 0; i < CARDSIZ;)
			card[i++] = *temp++;	/* copy to safe place */
		length = CARDSIZ;
	}
	else {
		if ((fildes = open(scan,0)) < 0)
			abend("can't open");
		length = read(fildes,card,CARDSIZ);
		close(fildes);
	}
	found = state = 0;
	scan = 0;
	for (i = 0; i<length && (c = card[i]) != '\n'; i++) {
		switch (state) {
		case 0:
			state = (c == '<'); break;
		case 1:
			state = (c == ':')?2:0; break;
		case 2:
			if (c == 't')
				state = 3;
			else if (c != ' ')
				state = 5;
			break;
		case 3:
			if (c == ' ')
				state = 2;
			else {
				scan = &card[i];
				state = 4;
			}
			break;
		case 4:
			if (c == ' ') {
				endspec = &card[i];
				state = 5;
			}
			else if (c == ':') {
				endspec = &card[i];
				state = 6;
			}
			break;
		case 5:
			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 card and found to be set*/

fwrite()
{
	if(TSPEC2[0] == '-' && TSPEC2[1] == '-')
		if (found)
			output(card);
		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 R.L away */
		for (i = 0; i <= 9; i++)
			if ((srelev[i] = getchal()) < 0)
				return(-1);	/* EOF or error */
		if (srelev[4] != '.' || srelev[9] != '\t')
			abend("not SCCS -m");
	}
	nextcol = 1;
	p = pfirst = line+NCOLS;	/* start at middle of workarea */
	do {
		if (fin.nleft-- > 0)
			c = *fin.nextp++;	/* c = getchal() */
		else if ((c = getchal()) < 0)		/* once per buffer */
			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)
		abend("line too long");
	return(c);
}

/*	getchal: local variant of getchar with stop mode processing:
	note EOF return is -1 rather than 0 */
getchal()
{
	if (fin.nleft <= 0) {
		/* make sure output flushed if last char was newline,
		thus allowing nroff -s or .rd requests, with small overhead */
		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)
int n;
{
	register char *p;
	p = pfirst;
	if (pfirst < linel+n)
		abend("line too long");
	while(n--) *--p = ' ';
	pfirst = p;
	return;
}

/*	truncate: truncate line to n chars max */
truncate(n)
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)
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)
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)
int n;
{
	pfirst = ((pfirst =+ n)<plast)?pfirst:plast;
	return;
}

/*	sccsadd: add SCCS release & level back onto end of line */
sccsadd()
{
	register char *p;
	register int i;
	p = plast;		/* current loc of '\n' */
	for (i = 0; i <= 3; i++)
		*p++ = srelev[i];	/* release */
	for (i = 5; i <= 8; i++)
		*p++ = srelev[i];	/* level */
	*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(str1)
char *str1;
{
	register char c;
	register char *str;
	str = str1;
	while (c = *str++) {
		putc(c,&fout);
		if (c == '\n') break;
	}
	return;
}

/*	abend: terminate with error and print diagnostic */
abend(mesg)
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 */
getnum(scan1)
/*	set scan ptr to addr of ending delimeter */
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);
}
