Let's try this.  It's BUGGERED.
diff --git a/src/cmd/tbl/t4.c b/src/cmd/tbl/t4.c
new file mode 100644
index 0000000..558d3ba
--- /dev/null
+++ b/src/cmd/tbl/t4.c
@@ -0,0 +1,405 @@
+/* t4.c: read table specification */
+# include "t.h"
+int	oncol;
+
+void
+getspec(void)
+{
+	int	icol, i;
+
+	qcol = findcol() + 1;/* must allow one extra for line at right */
+	garray(qcol);
+	sep[-1] = -1;
+	for (icol = 0; icol < qcol; icol++) {
+		sep[icol] = -1;
+		evenup[icol] = 0;
+		cll[icol][0] = 0;
+		for (i = 0; i < MAXHEAD; i++) {
+			csize[icol][i][0] = 0;
+			vsize[icol][i][0] = 0;
+			font[icol][i][0] = lefline[icol][i] = 0;
+			flags[icol][i] = 0;
+			style[icol][i] = 'l';
+		}
+	}
+	for (i = 0; i < MAXHEAD; i++)
+		lefline[qcol][i] = 0;	/* fixes sample55 looping */
+	nclin = ncol = 0;
+	oncol = 0;
+	left1flg = rightl = 0;
+	readspec();
+	Bprint(&tabout, ".rm");
+	for (i = 0; i < ncol; i++)
+		Bprint(&tabout, " %2s", reg(i, CRIGHT));
+	Bprint(&tabout, "\n");
+}
+
+
+void
+readspec(void)
+{
+	int	icol, c, sawchar, stopc, i;
+	char	sn[10], *snp, *temp;
+
+	sawchar = icol = 0;
+	while (c = get1char()) {
+		switch (c) {
+		default:
+			if (c != tab) {
+				char buf[64];
+				sprint(buf, "bad table specification character %c", c);
+				error(buf);
+			}
+		case ' ': /* note this is also case tab */
+			continue;
+		case '\n':
+			if (sawchar == 0) 
+				continue;
+		case ',':
+		case '.': /* end of table specification */
+			ncol = max(ncol, icol);
+			if (lefline[ncol][nclin] > 0) {
+				ncol++; 
+				rightl++;
+			};
+			if (sawchar)
+				nclin++;
+			if (nclin >= MAXHEAD)
+				error("too many lines in specification");
+			icol = 0;
+			if (ncol == 0 || nclin == 0)
+				error("no specification");
+			if (c == '.') {
+				while ((c = get1char()) && c != '\n')
+					if (c != ' ' && c != '\t')
+						error("dot not last character on format line");
+				/* fix up sep - default is 3 except at edge */
+				for (icol = 0; icol < ncol; icol++)
+					if (sep[icol] < 0)
+						sep[icol] =  icol + 1 < ncol ? 3 : 2;
+				if (oncol == 0)
+					oncol = ncol;
+				else if (oncol + 2 < ncol)
+					error("tried to widen table in T&, not allowed");
+				return;
+			}
+			sawchar = 0;
+			continue;
+		case 'C': 
+		case 'S': 
+		case 'R': 
+		case 'N': 
+		case 'L':  
+		case 'A':
+			c += ('a' - 'A');
+		case '_': 
+			if (c == '_') 
+				c = '-';
+		case '=': 
+		case '-':
+		case '^':
+		case 'c': 
+		case 's': 
+		case 'n': 
+		case 'r': 
+		case 'l':  
+		case 'a':
+			style[icol][nclin] = c;
+			if (c == 's' && icol <= 0)
+				error("first column can not be S-type");
+			if (c == 's' && style[icol-1][nclin] == 'a') {
+				Bprint(&tabout, ".tm warning: can't span a-type cols, changed to l\n");
+				style[icol-1][nclin] = 'l';
+			}
+			if (c == 's' && style[icol-1][nclin] == 'n') {
+				Bprint(&tabout, ".tm warning: can't span n-type cols, changed to c\n");
+				style[icol-1][nclin] = 'c';
+			}
+			icol++;
+			if (c == '^' && nclin <= 0)
+				error("first row can not contain vertical span");
+			if (icol > qcol)
+				error("too many columns in table");
+			sawchar = 1;
+			continue;
+		case 'b': 
+		case 'i':
+			c += 'A' - 'a';
+		case 'B': 
+		case 'I':
+			if (icol == 0) 
+				continue;
+			snp = font[icol-1][nclin];
+			snp[0] = (c == 'I' ? '2' : '3');
+			snp[1] = 0;
+			continue;
+		case 't': 
+		case 'T':
+			if (icol > 0)
+				flags[icol-1][nclin] |= CTOP;
+			continue;
+		case 'd': 
+		case 'D':
+			if (icol > 0)
+				flags[icol-1][nclin] |= CDOWN;
+			continue;
+		case 'f': 
+		case 'F':
+			if (icol == 0) 
+				continue;
+			snp = font[icol-1][nclin];
+			snp[0] = snp[1] = stopc = 0;
+			for (i = 0; i < 2; i++) {
+				c = get1char();
+				if (i == 0 && c == '(') {
+					stopc = ')';
+					c = get1char();
+				}
+				if (c == 0) 
+					break;
+				if (c == stopc) {
+					stopc = 0; 
+					break;
+				}
+				if (stopc == 0)  
+					if (c == ' ' || c == tab ) 
+						break;
+				if (c == '\n' || c == '|') {
+					un1getc(c); 
+					break;
+				}
+				snp[i] = c;
+				if (c >= '0' && c <= '9') 
+					break;
+			}
+			if (stopc) 
+				if (get1char() != stopc)
+					error("Nonterminated font name");
+			continue;
+		case 'P': 
+		case 'p':
+			if (icol <= 0) 
+				continue;
+			temp = snp = csize[icol-1][nclin];
+			while (c = get1char()) {
+				if (c == ' ' || c == tab || c == '\n') 
+					break;
+				if (c == '-' || c == '+')
+					if (snp > temp)
+						break;
+					else
+						*snp++ = c;
+				else if (digit(c))
+					*snp++ = c;
+				else 
+					break;
+				if (snp - temp > 4)
+					error("point size too large");
+			}
+			*snp = 0;
+			if (atoi(temp) > 36)
+				error("point size unreasonable");
+			un1getc (c);
+			continue;
+		case 'V': 
+		case 'v':
+			if (icol <= 0) 
+				continue;
+			temp = snp = vsize[icol-1][nclin];
+			while (c = get1char()) {
+				if (c == ' ' || c == tab || c == '\n') 
+					break;
+				if (c == '-' || c == '+')
+					if (snp > temp)
+						break;
+					else
+						*snp++ = c;
+				else if (digit(c))
+					*snp++ = c;
+				else 
+					break;
+				if (snp - temp > 4)
+					error("vertical spacing value too large");
+			}
+			*snp = 0;
+			un1getc(c);
+			continue;
+		case 'w': 
+		case 'W':
+			snp = cll [icol-1];
+			/* Dale Smith didn't like this check - possible to have two text blocks
+		   of different widths now ....
+			if (*snp)
+				{
+				Bprint(&tabout, "Ignored second width specification");
+				continue;
+				}
+		/* end commented out code ... */
+			stopc = 0;
+			while (c = get1char()) {
+				if (snp == cll[icol-1] && c == '(') {
+					stopc = ')';
+					continue;
+				}
+				if ( !stopc && (c > '9' || c < '0'))
+					break;
+				if (stopc && c == stopc)
+					break;
+				*snp++ = c;
+			}
+			*snp = 0;
+			if (snp - cll[icol-1] > CLLEN)
+				error ("column width too long");
+			if (!stopc)
+				un1getc(c);
+			continue;
+		case 'e': 
+		case 'E':
+			if (icol < 1) 
+				continue;
+			evenup[icol-1] = 1;
+			evenflg = 1;
+			continue;
+		case 'z': 
+		case 'Z': /* zero width-ignre width this item */
+			if (icol < 1) 
+				continue;
+			flags[icol-1][nclin] |= ZEROW;
+			continue;
+		case 'u': 
+		case 'U': /* half line up */
+			if (icol < 1) 
+				continue;
+			flags[icol-1][nclin] |= HALFUP;
+			continue;
+		case '0': 
+		case '1': 
+		case '2': 
+		case '3': 
+		case '4':
+		case '5': 
+		case '6': 
+		case '7': 
+		case '8': 
+		case '9':
+			sn[0] = c;
+			snp = sn + 1;
+			while (digit(*snp++ = c = get1char()))
+				;
+			un1getc(c);
+			sep[icol-1] = max(sep[icol-1], numb(sn));
+			continue;
+		case '|':
+			lefline[icol][nclin]++;
+			if (icol == 0) 
+				left1flg = 1;
+			continue;
+		}
+	}
+	error("EOF reading table specification");
+}
+
+
+int
+findcol(void)
+{
+# define FLNLIM 200
+	/* this counts the number of columns and then puts the line back*/
+	char	*s, line[FLNLIM+2], *p;
+	int	c, n = 0, inpar = 0;
+
+	while ((c = get1char()) != 0 && c == ' ')
+		;
+	if (c != '\n')
+		un1getc(c);
+	for (s = line; *s = c = get1char(); s++) {
+		if (c == ')') 
+			inpar = 0;
+		if (inpar) 
+			continue;
+		if (c == '\n' || c == 0 || c == '.' || c == ',')
+			break;
+		else if (c == '(')
+			inpar = 1;
+		else if (s >= line + FLNLIM)
+			error("too long spec line");
+	}
+	for (p = line; p < s; p++)
+		switch (*p) {
+		case 'l': 
+		case 'r': 
+		case 'c': 
+		case 'n': 
+		case 'a': 
+		case 's':
+		case 'L': 
+		case 'R': 
+		case 'C': 
+		case 'N': 
+		case 'A': 
+		case 'S':
+		case '-': 
+		case '=': 
+		case '_':
+			n++;
+		}
+	while (p >= line)
+		un1getc(*p--);
+	return(n);
+}
+
+
+void
+garray(int qcol)
+{
+	style =  (int (*)[]) getcore(MAXHEAD * qcol, sizeof(int));
+	evenup = (int *) getcore(qcol, sizeof(int));
+	lefline = (int (*)[]) getcore(MAXHEAD * (qcol + 1), sizeof (int)); /*+1 for sample55 loop - others may need it too*/
+	font = (char (*)[][2]) getcore(MAXHEAD * qcol, 2);
+	csize = (char (*)[MAXHEAD][4]) getcore(MAXHEAD * qcol, 4);
+	vsize = (char (*)[MAXHEAD][4]) getcore(MAXHEAD * qcol, 4);
+	flags =  (int (*)[]) getcore(MAXHEAD * qcol, sizeof(int));
+	cll = (char (*)[])getcore(qcol, CLLEN);
+	sep = (int *) getcore(qcol + 1, sizeof(int));
+	sep++; /* sep[-1] must be legal */
+	used = (int *) getcore(qcol + 1, sizeof(int));
+	lused = (int *) getcore(qcol + 1, sizeof(int));
+	rused = (int *) getcore(qcol + 1, sizeof(int));
+	doubled = (int *) getcore(qcol + 1, sizeof(int));
+	acase = (int *) getcore(qcol + 1, sizeof(int));
+	topat = (int *) getcore(qcol + 1, sizeof(int));
+}
+
+
+char	*
+getcore(int a, int b)
+{
+	char	*x;
+	x = calloc(a, b);
+	if (x == 0)
+		error("Couldn't get memory");
+	return(x);
+}
+
+
+void
+freearr(void)
+{
+	free(style);
+	free(evenup);
+	free(lefline);
+	free(flags);
+	free(font);
+	free(csize);
+	free(vsize);
+	free(cll);
+	free(--sep);	/* netnews says this should be --sep because incremented earlier! */
+	free(used);
+	free(lused);
+	free(rused);
+	free(doubled);
+	free(acase);
+	free(topat);
+}
+
+