xref: /illumos-gate/usr/src/cmd/tabs/tabs.c (revision 20a7641f9918de8574b8b3b47dbe35c4bfc78df1)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 1996 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 /*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
28 /*	  All Rights Reserved  	*/
29 
30 /*
31  *	tabs [tabspec] [+mn] [-Ttype]
32  *	set tabs (and margin, if +mn), for terminal type
33  */
34 
35 
36 #include <stdio.h>
37 #include <signal.h>
38 #include <sys/types.h>
39 #include <stdlib.h>
40 #include <fcntl.h>
41 #include <sys/stat.h>
42 #include <curses.h>
43 #include <term.h>
44 #include <locale.h>
45 #include <unistd.h>
46 #include <string.h>
47 #include <ctype.h>
48 #include <limits.h>
49 #include <signal.h>
50 
51 #define	EQ(a, b)	(strcmp(a, b) == 0)
52 /*	max # columns used (needed for GSI) */
53 #define	NCOLS	256
54 #define	NTABS	65	/* max # tabs +1 (to be set) */
55 #define	NTABSCL	21	/* max # tabs + 1 that will be cleared */
56 #define	ESC	033
57 #define	CLEAR	'2'
58 #define	SET	'1'
59 #define	TAB	'\t'
60 #define	CR	'\r'
61 #define	NMG	0	/* no margin setting */
62 #define	GMG	1	/* DTC300s margin */
63 #define	TMG	2	/* TERMINET margin */
64 #define	DMG	3	/* DASI450 margin */
65 #define	FMG	4	/* TTY 43 margin */
66 #define	TRMG	5	/* Trendata 4000a */
67 
68 #define	TCLRLN	0	/* long, repetitive, general tab clear */
69 
70 static char tsethp[] = {ESC,  '1', 0};		/* (default) */
71 static char tsetibm[] = {ESC, '0', 0};		/* ibm */
72 static char tclrhp[] = {ESC, '3', CR, 0};	/* hp terminals */
73 /* short sequence for many terminals */
74 static char tclrsh[] = {ESC, CLEAR, CR, 0};
75 static char tclrgs[] = {ESC, TAB, CR, 0};	/* short, for 300s */
76 static char tclr40[] = {ESC, 'R', CR, 0};	/* TTY 40/2, 4424 */
77 static char tclribm[] = {ESC, '1', CR, 0};	/* ibm */
78 
79 static struct ttab {
80 	char *ttype;	/* -Tttype */
81 	char *tclr;	/* char sequence to clear tabs and return carriage */
82 	int tmaxtab;	/* maximum allowed position */
83 } *tt;
84 
85 static struct ttab termtab[] = {
86 	"",		tclrsh,	132,
87 	"1620-12",	tclrsh,	158,
88 	"1620-12-8",	tclrsh,	158,
89 	"1700-12",	tclrsh,	132,
90 	"1700-12-8",	tclrsh,	158,
91 	"300-12",	TCLRLN,	158,
92 	"300s-12",	tclrgs,	158,
93 	"4424",		tclr40,	 80,
94 	"4000a",	tclrsh,	132,
95 	"4000a-12",	tclrsh,	158,
96 	"450-12",	tclrsh,	158,
97 	"450-12-8",	tclrsh,	158,
98 	"2631",		tclrhp, 240,
99 	"2631-c",	tclrhp, 240,
100 	"ibm",		tclribm, 80,
101 	0
102 };
103 
104 static int	err;
105 static int 	tmarg;
106 static char	settab[32], clear_tabs[32];
107 
108 static int	maxtab;		/* max tab for repetitive spec */
109 static int	margin;
110 static int	margflg;	/* >0 ==> +m option used, 0 ==> not */
111 static char	*terminal = "";
112 static char	*tabspec = "-8";	/* default tab specification */
113 
114 static struct termio ttyold;	/* tty table */
115 static int	ttyisave;	/* save for input modes */
116 static int	ttyosave;	/* save for output modes */
117 static int	istty;		/* 1 ==> is actual tty */
118 
119 static struct	stat	statbuf;
120 static char	*devtty;
121 
122 static void scantab(char *scan, int tabvect[NTABS], int level);
123 static void repetab(char *scan, int tabvect[NTABS]);
124 static void arbitab(char *scan, int tabvect[NTABS]);
125 static void filetab(char *scan, int tabvect[NTABS], int level);
126 static int getmarg(char *term);
127 static struct ttab *termadj();
128 static void settabs(int tabvect[NTABS]);
129 static char *cleartabs(register char *p, char *qq);
130 static int getnum(char **scan1);
131 static void endup();
132 static int stdtab(char option[], int tabvect[]);
133 static void usage();
134 static int chk_codes(char *codes);
135 
136 int
137 main(int argc, char **argv)
138 {
139 	int tabvect[NTABS];	/* build tab list here */
140 	char *scan;	/* scan pointer to next char */
141 	char operand[LINE_MAX];
142 	int option_end = 0;
143 
144 	(void) setlocale(LC_ALL, "");
145 
146 #if !defined(TEXT_DOMAIN)
147 #define	TEXT_DOMAIN "SYS_TEST"
148 #endif
149 	(void) textdomain(TEXT_DOMAIN);
150 
151 	(void) signal(SIGINT, endup);
152 	if (ioctl(1, TCGETA, &ttyold) == 0) {
153 		ttyisave = ttyold.c_iflag;
154 		ttyosave = ttyold.c_oflag;
155 		(void) fstat(1, &statbuf);
156 		devtty = ttyname(1);
157 		(void) chmod(devtty, 0000);	/* nobody, not even us */
158 		istty++;
159 	}
160 	tabvect[0] = 0;	/* mark as not yet filled in */
161 	while (--argc > 0) {
162 		scan = *++argv;
163 		if (*scan == '+') {
164 			if (!option_end) {
165 				if (*++scan == 'm') {
166 					margflg++;
167 					if (*++scan)
168 						margin = getnum(&scan);
169 					else
170 						margin = 10;
171 				} else {
172 					(void) fprintf(stderr, gettext(
173 				"tabs: %s: invalid tab spec\n"), scan-1);
174 					usage();
175 				}
176 			} else {
177 				/*
178 				 * only n1[,n2,...] operand can follow
179 				 * end of options delimiter "--"
180 				 */
181 				(void) fprintf(stderr, gettext(
182 				"tabs: %s: invalid tab stop operand\n"), scan);
183 				usage();
184 			}
185 		} else if (*scan == '-') {
186 			if (!option_end) {
187 				if (*(scan+1) == 'T') {
188 					/* allow space or no space after -T */
189 					if (*(scan+2) == '\0') {
190 						if (--argc > 0)
191 							terminal = *++argv;
192 						else
193 							usage();
194 					} else
195 						terminal = scan+2;
196 				} else if (*(scan+1) == '-')
197 					if (*(scan+2) == '\0')
198 						option_end = 1;
199 					else
200 						tabspec = scan; /* --file */
201 				else if (strcmp(scan+1, "code") == 0) {
202 					/* EMPTY */
203 					/* skip to next argument */
204 				} else if (chk_codes(scan+1) ||
205 				    (isdigit(*(scan+1)) && *(scan+2) == '\0')) {
206 					/*
207 					 * valid code or single digit decimal
208 					 * number
209 					 */
210 					tabspec = scan;
211 				} else {
212 					(void) fprintf(stderr, gettext(
213 					"tabs: %s: invalid tab spec\n"), scan);
214 					usage();
215 				}
216 			} else {
217 				/*
218 				 * only n1[,n2,...] operand can follow
219 				 * end of options delimiter "--"
220 				 */
221 				(void) fprintf(stderr, gettext(
222 				"tabs: %s: invalid tab stop operand\n"), scan);
223 				usage();
224 			}
225 		} else {
226 			/*
227 			 * Tab-stop values separated using either commas
228 			 * or blanks.  If any number (except the first one)
229 			 * is preceded by a plus sign, it is taken as an
230 			 * increment to be added to the previous value.
231 			 */
232 			operand[0] = '\0';
233 			while (argc > 0) {
234 				if (strrchr(*argv, '-') == (char *)NULL) {
235 					(void) strcat(operand, *argv);
236 					if (argc > 1)
237 						(void) strcat(operand, ",");
238 					--argc;
239 					++argv;
240 				} else {
241 					(void) fprintf(stderr, gettext(
242 		"tabs: %s: tab stop values must be positive integers\n"),
243 					    *argv);
244 					usage();
245 				}
246 			}
247 			tabspec = operand;	/* save tab specification */
248 		}
249 	}
250 	if (*terminal == '\0') {
251 		if ((terminal = getenv("TERM")) == (char *)NULL ||
252 		    *terminal == '\0') {
253 			/*
254 			 * Use tab setting and clearing sequences specified
255 			 * by the ANSI standard.
256 			 */
257 			terminal = "ansi+tabs";
258 		}
259 	}
260 	if (setupterm(terminal, 1, &err) == ERR) {
261 		(void) fprintf(stderr, gettext(
262 		"tabs: %s: terminfo file not found\n"), terminal);
263 		usage();
264 	} else if (!tigetstr("hts")) {
265 		(void) fprintf(stderr, gettext(
266 		"tabs: cannot set tabs on terminal type %s\n"), terminal);
267 		usage();
268 	}
269 	if (err <= 0 || columns <= 0 || set_tab == 0) {
270 		tt = termadj();
271 		if (strcmp(terminal, "ibm") == 0)
272 			(void) strcpy(settab, tsetibm);
273 		else
274 			(void) strcpy(settab, tsethp);
275 		(void) strcpy(clear_tabs, tt->tclr);
276 		maxtab = tt->tmaxtab;
277 	} else {
278 		maxtab = columns;
279 		(void) strcpy(settab, set_tab);
280 		(void) strcpy(clear_tabs, clear_all_tabs);
281 	}
282 	scantab(tabspec, tabvect, 0);
283 	if (!tabvect[0])
284 		repetab("8", tabvect);
285 	settabs(tabvect);
286 	endup();
287 	return (0);
288 }
289 
290 /*
291  * return 1 if code option is valid, otherwise return 0
292  */
293 int
294 chk_codes(char *code)
295 {
296 	if (*(code+1) == '\0' && (*code == 'a' || *code == 'c' ||
297 	    *code == 'f' || *code == 'p' || *code == 's' || *code == 'u'))
298 			return (1);
299 	else if (*(code+1) == '2' && *(code+2) == '\0' &&
300 	    (*code == 'a' || *code == 'c'))
301 			return (1);
302 	else if (*code == 'c' && *(code+1) == '3' && *(code+2) == '\0')
303 		return (1);
304 	return (0);
305 }
306 
307 /*	scantab: scan 1 tabspec & return tab list for it */
308 void
309 scantab(char *scan, int tabvect[NTABS], int level)
310 {
311 	char c;
312 	if (*scan == '-') {
313 		if ((c = *++scan) == '-')
314 			filetab(++scan, tabvect, level);
315 		else if (c >= '0' && c <= '9')
316 			repetab(scan, tabvect);
317 		else if (stdtab(scan, tabvect)) {
318 			endup();
319 			(void) fprintf(stderr, gettext(
320 			"tabs: %s: unknown tab code\n"), scan);
321 			usage();
322 		}
323 	} else {
324 		arbitab(scan, tabvect);
325 	}
326 }
327 
328 /*	repetab: scan and set repetitve tabs, 1+n, 1+2*n, etc */
329 
330 void
331 repetab(char *scan, int tabvect[NTABS])
332 {
333 	int incr, i, tabn;
334 	int limit;
335 	incr = getnum(&scan);
336 	tabn = 1;
337 	limit = (maxtab-1)/(incr?incr:1)-1; /* # last actual tab */
338 	if (limit > NTABS-2)
339 		limit = NTABS-2;
340 	for (i = 0; i <= limit; i++)
341 		tabvect[i] = tabn += incr;
342 	tabvect[i] = 0;
343 }
344 
345 /*	arbitab: handle list of arbitrary tabs */
346 
347 void
348 arbitab(char *scan, int tabvect[NTABS])
349 {
350 	char *scan_save;
351 	int i, t, last;
352 
353 	scan_save = scan;
354 	last = 0;
355 	for (i = 0; i < NTABS-1; ) {
356 		if (*scan == '+') {
357 			scan++;		/* +n ==> increment, not absolute */
358 			if (t = getnum(&scan))
359 				tabvect[i++] = last += t;
360 			else {
361 				endup();
362 				(void) fprintf(stderr, gettext(
363 				"tabs: %s: invalid increment\n"), scan_save);
364 				usage();
365 			}
366 		} else {
367 			if ((t = getnum(&scan)) > last)
368 				tabvect[i++] = last = t;
369 			else {
370 				endup();
371 				(void) fprintf(stderr, gettext(
372 				"tabs: %s: invalid tab stop\n"), scan_save);
373 				usage();
374 			}
375 		}
376 		if (*scan++ != ',') break;
377 	}
378 	if (last > NCOLS) {
379 		endup();
380 		(void) fprintf(stderr, gettext(
381 	"tabs: %s: last tab stop would be set at a column greater than %d\n"),
382 		    scan_save, NCOLS);
383 		usage();
384 	}
385 	tabvect[i] = 0;
386 }
387 
388 /*	filetab: copy tabspec from existing file */
389 #define	CARDSIZ	132
390 
391 void
392 filetab(char *scan, int tabvect[NTABS], int level)
393 {
394 	int length, i;
395 	char c;
396 	int fildes;
397 	char card[CARDSIZ];	/* buffer area for 1st card in file */
398 	char state, found;
399 	char *temp;
400 	if (level) {
401 		endup();
402 		(void) fprintf(stderr, gettext(
403 		"tabs: %s points to another file: invalid file indirection\n"),
404 		    scan);
405 		exit(1);
406 	}
407 	if ((fildes = open(scan, O_RDONLY)) < 0) {
408 		endup();
409 		(void) fprintf(stderr, gettext("tabs: %s: "), scan);
410 		perror("");
411 		exit(1);
412 	}
413 	length = read(fildes, card, CARDSIZ);
414 	(void) close(fildes);
415 	found = state = 0;
416 	scan = 0;
417 	for (i = 0; i < length && (c = card[i]) != '\n'; i++) {
418 		switch (state) {
419 		case 0:
420 			state = (c == '<'); break;
421 		case 1:
422 			state = (c == ':')?2:0; break;
423 		case 2:
424 			if (c == 't')
425 				state = 3;
426 			else if (c == ':')
427 				state = 6;
428 			else if (c != ' ')
429 				state = 5;
430 			break;
431 		case 3:
432 			if (c == ' ')
433 				state = 2;
434 			else {
435 				scan = &card[i];
436 				state = 4;
437 			}
438 			break;
439 		case 4:
440 			if (c == ' ') {
441 				card[i] = '\0';
442 				state = 5;
443 			} else if (c == ':') {
444 				card[i] = '\0';
445 				state = 6;
446 			}
447 			break;
448 		case 5:
449 			if (c == ' ')
450 				state = 2;
451 			else if (c == ':')
452 				state = 6;
453 			break;
454 		case 6:
455 			if (c == '>') {
456 				found = 1;
457 				goto done;
458 			} else state = 5;
459 			break;
460 		}
461 	}
462 done:
463 	if (found && scan != 0) {
464 		scantab(scan, tabvect, 1);
465 		temp = scan;
466 		while (*++temp)
467 			;
468 		*temp = '\n';
469 	}
470 	else
471 		scantab("-8", tabvect, 1);
472 }
473 
474 int
475 getmarg(char *term)
476 {
477 	if (strncmp(term, "1620", 4) == 0 ||
478 	    strncmp(term, "1700", 4) == 0 || strncmp(term, "450", 3) == 0)
479 		return (DMG);
480 	else if (strncmp(term, "300s", 4) == 0)
481 		return (GMG);
482 	else if (strncmp(term, "4000a", 5) == 0)
483 		return (TRMG);
484 	else if (strcmp(term, "43") == 0)
485 		return (FMG);
486 	else if (strcmp(term, "tn300") == 0 || strcmp(term, "tn1200") == 0)
487 		return (TMG);
488 	else
489 		return (NMG);
490 }
491 
492 
493 
494 struct ttab *
495 termadj(void)
496 {
497 	struct ttab *t;
498 
499 	if (strncmp(terminal, "40-2", 4) == 0 || strncmp(terminal,
500 	    "40/2", 4) == 0 || strncmp(terminal, "4420", 4) == 0)
501 		(void) strcpy(terminal, "4424");
502 	else if (strncmp(terminal, "ibm", 3) == 0 || strcmp(terminal,
503 	    "3101") == 0 || strcmp(terminal, "system1") == 0)
504 		(void) strcpy(terminal, "ibm");
505 
506 	for (t = termtab; t->ttype; t++) {
507 		if (EQ(terminal, t->ttype))
508 			return (t);
509 	}
510 /* should have message */
511 	return (termtab);
512 }
513 
514 char	*cleartabs();
515 /*
516  *	settabs: set actual tabs at terminal
517  *	note: this code caters to necessities of handling GSI and
518  *	other terminals in a consistent way.
519  */
520 
521 void
522 settabs(int tabvect[NTABS])
523 {
524 	char setbuf[512];	/* 2+3*NTABS+2+NCOLS+NTABS (+ some extra) */
525 	char *p;		/* ptr for assembly in setbuf */
526 	int *curtab;		/* ptr to tabvect item */
527 	int i, previous, nblanks;
528 	if (istty) {
529 		ttyold.c_iflag &= ~ICRNL;
530 		ttyold.c_oflag &= ~(ONLCR|OCRNL|ONOCR|ONLRET);
531 		(void) ioctl(1, TCSETAW, &ttyold);	/* turn off cr-lf map */
532 	}
533 	p = setbuf;
534 	*p++ = CR;
535 	p = cleartabs(p, clear_tabs);
536 
537 	if (margflg) {
538 		tmarg = getmarg(terminal);
539 		switch (tmarg) {
540 		case GMG:	/* GSI300S */
541 		/*
542 		 * NOTE: the 300S appears somewhat odd, in that there is
543 		 * a column 0, but there is no way to do a direct tab to it.
544 		 * The sequence ESC 'T' '\0' jumps to column 27 and prints
545 		 * a '0', without changing the margin.
546 		 */
547 			*p++ = ESC;
548 			*p++ = 'T';	/* setup for direct tab */
549 			if (margin &= 0177)	/* normal case */
550 				*p++ = margin;
551 			else {			/* +m0 case */
552 				*p++ = 1;	/* column 1 */
553 				*p++ = '\b';	/* column 0 */
554 			}
555 			*p++ = margin;	/* direct horizontal tab */
556 			*p++ = ESC;
557 			*p++ = '0';	/* actual margin set */
558 			break;
559 		case TMG:	/* TERMINET 300 & 1200 */
560 			while (margin--)
561 				*p++ = ' ';
562 			break;
563 		case DMG:	/* DASI450/DIABLO 1620 */
564 			*p++ = ESC;	/* direct tab ignores margin */
565 			*p++ = '\t';
566 			if (margin == 3) {
567 				*p++ = (margin & 0177);
568 				*p++ = ' ';
569 			}
570 			else
571 				*p++ = (margin & 0177) + 1;
572 			*p++ = ESC;
573 			*p++ = '9';
574 			break;
575 		case FMG:	/* TTY 43 */
576 			p--;
577 			*p++ = ESC;
578 			*p++ = 'x';
579 			*p++ = CR;
580 			while (margin--)
581 				*p++ = ' ';
582 			*p++ = ESC;
583 			*p++ = 'l';
584 			*p++ = CR;
585 			(void) write(1, setbuf, p - setbuf);
586 			return;
587 		case TRMG:
588 			p--;
589 			*p++ = ESC;
590 			*p++ = 'N';
591 			while (margin--)
592 				*p++ = ' ';
593 			*p++ = ESC;
594 			*p++ = 'F';
595 			break;
596 		}
597 	}
598 
599 /*
600  *	actual setting: at least terminals do this consistently!
601  */
602 	previous = 1; curtab = tabvect;
603 	while ((nblanks = *curtab-previous) >= 0 &&
604 	    previous + nblanks <= maxtab) {
605 		for (i = 1; i <= nblanks; i++) *p++ = ' ';
606 		previous = *curtab++;
607 		(void) strcpy(p, settab);
608 		p += strlen(settab);
609 	}
610 	*p++ = CR;
611 	if (EQ(terminal, "4424"))
612 		*p++ = '\n';	/* TTY40/2 needs LF, not just CR */
613 	(void) write(1, setbuf, p - setbuf);
614 }
615 
616 
617 /*
618  *	Set software tabs.  This only works on UNIX/370 using a series/1
619  *	front-end processor.
620  */
621 
622 
623 /*	cleartabs(pointer to buffer, pointer to clear sequence) */
624 char *
625 cleartabs(register char *p, char *qq)
626 {
627 	int i;
628 	char *q;
629 	q = qq;
630 	if (clear_tabs == 0) {		/* if repetitive sequence */
631 		*p++ = CR;
632 		for (i = 0; i < NTABSCL - 1; i++) {
633 			*p++ = TAB;
634 			*p++ = ESC;
635 			*p++ = CLEAR;
636 		}
637 		*p++ = CR;
638 	} else {
639 		while (*p++ = *q++)	/* copy table sequence */
640 			;
641 		p--;			/* adjust for null */
642 		if (EQ(terminal, "4424")) {	/* TTY40 extra delays needed */
643 			*p++ = '\0';
644 			*p++ = '\0';
645 			*p++ = '\0';
646 			*p++ = '\0';
647 		}
648 	}
649 	return (p);
650 }
651 /*	getnum: scan and convert number, return zero if none found */
652 /*	set scan ptr to addr of ending delimeter */
653 int
654 getnum(char **scan1)
655 {
656 	int n;
657 	char c, *scan;
658 	n = 0;
659 	scan = *scan1;
660 	while ((c = *scan++) >= '0' && c <= '9') n = n * 10 + c -'0';
661 	*scan1 = --scan;
662 	return (n);
663 }
664 
665 /*	usage: terminate processing with usage message */
666 void
667 usage(void)
668 {
669 	(void) fprintf(stderr, gettext(
670 "usage: tabs [ -n| --file| [[-code] -a| -a2| -c| -c2| -c3| -f| -p| -s| -u]] \
671 [+m[n]] [-T type]\n"));
672 
673 	(void) fprintf(stderr, gettext(
674 "       tabs [-T type][+m[n]] n1[,n2,...]\n"));
675 
676 	endup();
677 	exit(1);
678 }
679 
680 /*	endup: make sure tty mode reset & exit */
681 void
682 endup(void)
683 {
684 
685 	if (istty) {
686 		ttyold.c_iflag = ttyisave;
687 		ttyold.c_oflag = ttyosave;
688 		/* reset cr-lf to previous */
689 		(void) ioctl(1, TCSETAW, &ttyold);
690 		(void) chmod(devtty, statbuf.st_mode);
691 	}
692 	if (err > 0) {
693 		(void) resetterm();
694 	}
695 }
696 
697 /*
698  *	stdtabs: standard tabs table
699  *	format: option code letter(s), null, tabs, null
700  */
701 static char stdtabs[] = {
702 'a',	0, 1, 10, 16, 36, 72, 0,		/* IBM 370 Assembler */
703 'a', '2', 0, 1, 10, 16, 40, 72, 0,		/* IBM Assembler alternative */
704 'c',	0, 1, 8, 12, 16, 20, 55, 0,		/* COBOL, normal */
705 'c', '2', 0, 1, 6, 10, 14, 49, 0,		/* COBOL, crunched */
706 'c', '3', 0, 1, 6, 10, 14, 18, 22, 26, 30, 34, 38, 42, 46, 50, 54, 58, 62, 67,
707 	0,					/* crunched COBOL, many tabs */
708 'f',	0, 1, 7, 11, 15, 19, 23, 0,		/* FORTRAN */
709 'p',	0, 1, 5, 9, 13, 17, 21, 25, 29, 33, 37, 41, 45, 49, 53, 57, 61, 0,
710 						/* PL/I */
711 's',	0, 1, 10, 55, 0, 			/* SNOBOL */
712 'u',	0, 1, 12, 20, 44, 0,			/* UNIVAC ASM */
713 0};
714 
715 /*
716  *	stdtab: return tab list for any "canned" tab option.
717  *	entry: option points to null-terminated option string
718  *		tabvect points to vector to be filled in
719  *	exit: return (0) if legal, tabvect filled, ending with zero
720  *		return (-1) if unknown option
721  */
722 int
723 stdtab(char option[], int tabvect[])
724 {
725 	char *sp;
726 	tabvect[0] = 0;
727 	sp = stdtabs;
728 	while (*sp) {
729 		if (EQ(option, sp)) {
730 			while (*sp++)		/* skip to 1st tab value */
731 				;
732 			while (*tabvect++ = *sp++)	/* copy, make int */
733 				;
734 			return (0);
735 		}
736 		while (*sp++)	/* skip to 1st tab value */
737 			;
738 		while (*sp++)		/* skip over tab list */
739 			;
740 	}
741 	return (-1);
742 }
743