xref: /openbsd-src/usr.bin/deroff/deroff.c (revision a28daedfc357b214be5c701aa8ba8adb29a7f1c2)
1 /*	$OpenBSD: deroff.c,v 1.7 2005/07/05 18:27:41 jmc Exp $	*/
2 
3 /*-
4  * Copyright (c) 1988, 1993
5  *	The Regents of the University of California.  All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  * 3. Neither the name of the University nor the names of its contributors
16  *    may be used to endorse or promote products derived from this software
17  *    without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29  * SUCH DAMAGE.
30  */
31 /*
32  * Copyright (C) Caldera International Inc.  2001-2002.
33  * All rights reserved.
34  *
35  * Redistribution and use in source and binary forms, with or without
36  * modification, are permitted provided that the following conditions
37  * are met:
38  * 1. Redistributions of source code and documentation must retain the above
39  *    copyright notice, this list of conditions and the following disclaimer.
40  * 2. Redistributions in binary form must reproduce the above copyright
41  *    notice, this list of conditions and the following disclaimer in the
42  *    documentation and/or other materials provided with the distribution.
43  * 3. All advertising materials mentioning features or use of this software
44  *    must display the following acknowledgement:
45  *	This product includes software developed or owned by Caldera
46  *	International, Inc.
47  * 4. Neither the name of Caldera International, Inc. nor the names of other
48  *    contributors may be used to endorse or promote products derived from
49  *    this software without specific prior written permission.
50  *
51  * USE OF THE SOFTWARE PROVIDED FOR UNDER THIS LICENSE BY CALDERA
52  * INTERNATIONAL, INC. AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR
53  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
54  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
55  * IN NO EVENT SHALL CALDERA INTERNATIONAL, INC. BE LIABLE FOR ANY DIRECT,
56  * INDIRECT INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
57  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
58  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
59  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
60  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
61  * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
62  * POSSIBILITY OF SUCH DAMAGE.
63  */
64 
65 #ifndef lint
66 static const char copyright[] =
67 "@(#) Copyright (c) 1988, 1993\n\
68 	The Regents of the University of California.  All rights reserved.\n";
69 #endif /* not lint */
70 
71 #ifndef lint
72 #if 0
73 static const char sccsid[] = "@(#)deroff.c	8.1 (Berkeley) 6/6/93";
74 #else
75 static const char rcsid[] = "$OpenBSD: deroff.c,v 1.7 2005/07/05 18:27:41 jmc Exp $";
76 #endif
77 #endif /* not lint */
78 
79 #include <err.h>
80 #include <limits.h>
81 #include <stdio.h>
82 #include <stdlib.h>
83 #include <string.h>
84 #include <unistd.h>
85 
86 /*
87  *	Deroff command -- strip troff, eqn, and Tbl sequences from
88  *	a file.  Has two flags argument, -w, to cause output one word per line
89  *	rather than in the original format.
90  *	-mm (or -ms) causes the corresponding macro's to be interpreted
91  *	so that just sentences are output
92  *	-ml  also gets rid of lists.
93  *	Deroff follows .so and .nx commands, removes contents of macro
94  *	definitions, equations (both .EQ ... .EN and $...$),
95  *	Tbl command sequences, and Troff backslash constructions.
96  *
97  *	All input is through the Cget macro;
98  *	the most recently read character is in c.
99  *
100  *	Modified by Robert Henry to process -me and -man macros.
101  */
102 
103 #define Cget ( (c=getc(infile)) == EOF ? eof() : ((c==ldelim)&&(filesp==files) ? skeqn() : c) )
104 #define C1get ( (c=getc(infile)) == EOF ? eof() :  c)
105 
106 #ifdef DEBUG
107 #  define C	_C()
108 #  define C1	_C1()
109 #else /* not DEBUG */
110 #  define C	Cget
111 #  define C1	C1get
112 #endif /* not DEBUG */
113 
114 #define SKIP while (C != '\n')
115 #define SKIP_TO_COM SKIP; SKIP; pc=c; while (C != '.' || pc != '\n' || C > 'Z')pc=c
116 
117 #define	YES 1
118 #define	NO 0
119 #define	MS 0	/* -ms */
120 #define	MM 1	/* -mm */
121 #define	ME 2	/* -me */
122 #define	MA 3	/* -man */
123 
124 #ifdef DEBUG
125 char *mactab[] = { "-ms", "-mm", "-me", "-ma" };
126 #endif /* DEBUG */
127 
128 #define	ONE 1
129 #define	TWO 2
130 
131 #define NOCHAR -2
132 #define SPECIAL 0
133 #define APOS 1
134 #define PUNCT 2
135 #define DIGIT 3
136 #define LETTER 4
137 
138 #define MAXFILES 20
139 
140 int	iflag;
141 int	wordflag;
142 int	msflag;		/* processing a source written using a mac package */
143 int	mac;		/* which package */
144 int	disp;
145 int	parag;
146 int	inmacro;
147 int	intable;
148 int	keepblock;	/* keep blocks of text; normally false when msflag */
149 
150 char chars[128];  /* SPECIAL, PUNCT, APOS, DIGIT, or LETTER */
151 
152 char line[LINE_MAX];
153 char *lp;
154 
155 int c;
156 int pc;
157 int ldelim;
158 int rdelim;
159 
160 char fname[PATH_MAX];
161 FILE *files[MAXFILES];
162 FILE **filesp;
163 FILE *infile;
164 
165 int argc;
166 char **argv;
167 
168 /*
169  *	Macro processing
170  *
171  *	Macro table definitions
172  */
173 typedef	int pacmac;		/* compressed macro name */
174 int	argconcat = 0;		/* concat arguments together (-me only) */
175 
176 #define	tomac(c1, c2)		((((c1) & 0xFF) << 8) | ((c2) & 0xFF))
177 #define	frommac(src, c1, c2)	(((c1)=((src)>>8)&0xFF),((c2) =(src)&0xFF))
178 
179 struct mactab{
180 	int	condition;
181 	pacmac	macname;
182 	int	(*func)();	/* XXX - args */
183 };
184 
185 struct	mactab	troffmactab[];
186 struct	mactab	ppmactab[];
187 struct	mactab	msmactab[];
188 struct	mactab	mmmactab[];
189 struct	mactab	memactab[];
190 struct	mactab	manmactab[];
191 
192 /*
193  *	Macro table initialization
194  */
195 #define	M(cond, c1, c2, func) {cond, tomac(c1, c2), func}
196 
197 /*
198  *	Flags for matching conditions other than
199  *	the macro name
200  */
201 #define	NONE		0
202 #define	FNEST		1		/* no nested files */
203 #define	NOMAC		2		/* no macro */
204 #define	MAC		3		/* macro */
205 #define	PARAG		4		/* in a paragraph */
206 #define	MSF		5		/* msflag is on */
207 #define	NBLK		6		/* set if no blocks to be kept */
208 
209 /*
210  *	Return codes from macro minions, determine where to jump,
211  *	how to repeat/reprocess text
212  */
213 #define	COMX		1		/* goto comx */
214 #define	COM		2		/* goto com */
215 
216 int	 skeqn(void);
217 int	 eof(void);
218 int	 _C1(void);
219 int	 _C(void);
220 int	 EQ(void);
221 int	 domacro(void);
222 int	 PS(void);
223 int	 skip(void);
224 int	 intbl(void);
225 int	 outtbl(void);
226 int	 so(void);
227 int	 nx(void);
228 int	 skiptocom(void);
229 int	 PP(pacmac);
230 int	 AU(void);
231 int	 SH(pacmac);
232 int	 UX(void);
233 int	 MMHU(pacmac);
234 int	 mesnblock(pacmac);
235 int	 mssnblock(pacmac);
236 int	 nf(void);
237 int	 ce(void);
238 int	 meip(pacmac);
239 int	 mepp(pacmac);
240 int	 mesh(pacmac);
241 int	 mefont(pacmac);
242 int	 manfont(pacmac);
243 int	 manpp(pacmac);
244 int	 macsort(const void *, const void *);
245 int	 sizetab(struct mactab *);
246 void	 getfname(void);
247 void	 textline(char *, int);
248 void	 work(void);
249 void	 regline(void (*)(char *, int), int);
250 void	 macro(void);
251 void	 tbl(void);
252 void	 stbl(void);
253 void	 eqn(void);
254 void	 backsl(void);
255 void	 sce(void);
256 void	 refer(int);
257 void	 inpic(void);
258 void	 msputmac(char *, int);
259 void	 msputwords(int);
260 void	 meputmac(char *, int);
261 void	 meputwords(int);
262 void	 noblock(char, char);
263 void	 defcomline(pacmac);
264 void	 comline(void);
265 void	 buildtab(struct mactab **, int *);
266 FILE	*opn(char *);
267 struct mactab *macfill(struct mactab *, struct mactab *);
268 __dead void usage(void);
269 
270 int
271 main(int ac, char **av)
272 {
273 	int	i, ch;
274 	int	errflg = 0;
275 	int	kflag = NO;
276 
277 	iflag = NO;
278 	wordflag = NO;
279 	msflag = NO;
280 	mac = ME;
281 	disp = NO;
282 	parag = NO;
283 	inmacro = NO;
284 	intable = NO;
285 	ldelim	= NOCHAR;
286 	rdelim	= NOCHAR;
287 	keepblock = YES;
288 
289 	while ((ch = getopt(ac, av, "ikpwm:")) != -1) {
290 		switch (ch) {
291 		case 'i':
292 			iflag = YES;
293 			break;
294 		case 'k':
295 			kflag = YES;
296 			break;
297 		case 'm':
298 			msflag = YES;
299 			keepblock = NO;
300 			switch (optarg[0]) {
301 			case 'm':
302 				mac = MM;
303 				break;
304 			case 's':
305 				mac = MS;
306 				break;
307 			case 'e':
308 				mac = ME;
309 				break;
310 			case 'a':
311 				mac = MA;
312 				break;
313 			case 'l':
314 				disp = YES;
315 				break;
316 			default:
317 				errflg++;
318 				break;
319 			}
320 			if (errflg == 0 && optarg[1] != '\0')
321 				errflg++;
322 			break;
323 		case 'p':
324 			parag = YES;
325 			break;
326 		case 'w':
327 			wordflag = YES;
328 			kflag = YES;
329 			break;
330 		default:
331 			errflg++;
332 		}
333 	}
334 	argc = ac - optind;
335 	argv = av + optind;
336 
337 	if (kflag)
338 		keepblock = YES;
339 	if (errflg)
340 		usage();
341 
342 #ifdef DEBUG
343 	printf("msflag = %d, mac = %s, keepblock = %d, disp = %d\n",
344 		msflag, mactab[mac], keepblock, disp);
345 #endif /* DEBUG */
346 	if (argc == 0) {
347 		infile = stdin;
348 	} else {
349 		infile = opn(argv[0]);
350 		--argc;
351 		++argv;
352 	}
353 	files[0] = infile;
354 	filesp = &files[0];
355 
356 	for (i = 'a'; i <= 'z' ; ++i)
357 		chars[i] = LETTER;
358 	for (i = 'A'; i <= 'Z'; ++i)
359 		chars[i] = LETTER;
360 	for (i = '0'; i <= '9'; ++i)
361 		chars[i] = DIGIT;
362 	chars['\''] = APOS;
363 	chars['&'] = APOS;
364 	chars['.'] = PUNCT;
365 	chars[','] = PUNCT;
366 	chars[';'] = PUNCT;
367 	chars['?'] = PUNCT;
368 	chars[':'] = PUNCT;
369 	work();
370 	exit(0);
371 }
372 
373 int
374 skeqn(void)
375 {
376 
377 	while ((c = getc(infile)) != rdelim) {
378 		if (c == EOF)
379 			c = eof();
380 		else if (c == '"') {
381 			while ((c = getc(infile)) != '"') {
382 				if (c == EOF ||
383 				    (c == '\\' && (c = getc(infile)) == EOF))
384 					c = eof();
385 			}
386 		}
387 	}
388 	if (msflag)
389 		return((c = 'x'));
390 	return((c = ' '));
391 }
392 
393 FILE *
394 opn(char *p)
395 {
396 	FILE *fd;
397 
398 	if ((fd = fopen(p, "r")) == NULL)
399 		err(1, "fopen %s", p);
400 
401 	return(fd);
402 }
403 
404 int
405 eof(void)
406 {
407 
408 	if (infile != stdin)
409 		fclose(infile);
410 	if (filesp > files)
411 		infile = *--filesp;
412 	else if (argc > 0) {
413 		infile = opn(argv[0]);
414 		--argc;
415 		++argv;
416 	} else
417 		exit(0);
418 	return(C);
419 }
420 
421 void
422 getfname(void)
423 {
424 	char *p;
425 	struct chain {
426 		struct chain *nextp;
427 		char *datap;
428 	} *q;
429 	static struct chain *namechain= NULL;
430 
431 	while (C == ' ')
432 		;	/* nothing */
433 
434 	for (p = fname ; p - fname < sizeof(fname) && (*p = c) != '\n' &&
435 	    c != ' ' && c != '\t' && c != '\\'; ++p)
436 		C;
437 	*p = '\0';
438 	while (c != '\n')
439 		C;
440 
441 	/* see if this name has already been used */
442 	for (q = namechain ; q; q = q->nextp)
443 		if (strcmp(fname, q->datap) == 0) {
444 			fname[0] = '\0';
445 			return;
446 		}
447 
448 	q = (struct chain *) malloc(sizeof(struct chain));
449 	if (q == NULL)
450 		err(1, NULL);
451 	q->nextp = namechain;
452 	q->datap = strdup(fname);
453 	if (q->datap == NULL)
454 		err(1, NULL);
455 	namechain = q;
456 }
457 
458 /*ARGSUSED*/
459 void
460 textline(char *str, int constant)
461 {
462 
463 	if (wordflag) {
464 		msputwords(0);
465 		return;
466 	}
467 	puts(str);
468 }
469 
470 void
471 work(void)
472 {
473 
474 	for (;;) {
475 		C;
476 #ifdef FULLDEBUG
477 		printf("Starting work with `%c'\n", c);
478 #endif /* FULLDEBUG */
479 		if (c == '.' || c == '\'')
480 			comline();
481 		else
482 			regline(textline, TWO);
483 	}
484 }
485 
486 void
487 regline(void (*pfunc)(char *, int), int constant)
488 {
489 
490 	line[0] = c;
491 	lp = line;
492 	while (lp - line < sizeof(line)) {
493 		if (c == '\\') {
494 			*lp = ' ';
495 			backsl();
496 		}
497 		if (c == '\n')
498 			break;
499 		if (intable && c == 'T') {
500 			*++lp = C;
501 			if (c == '{' || c == '}') {
502 				lp[-1] = ' ';
503 				*lp = C;
504 			}
505 		} else {
506 			*++lp = C;
507 		}
508 	}
509 	*lp = '\0';
510 
511 	if (line[0] != '\0')
512 		(*pfunc)(line, constant);
513 }
514 
515 void
516 macro(void)
517 {
518 
519 	if (msflag) {
520 		do {
521 			SKIP;
522 		} while (C!='.' || C!='.' || C=='.');	/* look for  .. */
523 		if (c != '\n')
524 			SKIP;
525 		return;
526 	}
527 	SKIP;
528 	inmacro = YES;
529 }
530 
531 void
532 tbl(void)
533 {
534 
535 	while (C != '.')
536 		;	/* nothing */
537 	SKIP;
538 	intable = YES;
539 }
540 
541 void
542 stbl(void)
543 {
544 
545 	while (C != '.')
546 		;	/* nothing */
547 	SKIP_TO_COM;
548 	if (c != 'T' || C != 'E') {
549 		SKIP;
550 		pc = c;
551 		while (C != '.' || pc != '\n' || C != 'T' || C != 'E')
552 			pc = c;
553 	}
554 }
555 
556 void
557 eqn(void)
558 {
559 	int c1, c2;
560 	int dflg;
561 	char last;
562 
563 	last=0;
564 	dflg = 1;
565 	SKIP;
566 
567 	for (;;) {
568 		if (C1 == '.'  || c == '\'') {
569 			while (C1 == ' ' || c == '\t')
570 				;
571 			if (c == 'E' && C1 == 'N') {
572 				SKIP;
573 				if (msflag && dflg) {
574 					putchar('x');
575 					putchar(' ');
576 					if (last) {
577 						putchar(last);
578 						putchar('\n');
579 					}
580 				}
581 				return;
582 			}
583 		} else if (c == 'd') {
584 			/* look for delim */
585 			if (C1 == 'e' && C1 == 'l')
586 				if (C1 == 'i' && C1 == 'm') {
587 					while (C1 == ' ')
588 						;	/* nothing */
589 
590 					if ((c1 = c) == '\n' ||
591 					    (c2 = C1) == '\n' ||
592 					    (c1 == 'o' && c2 == 'f' && C1=='f')) {
593 						ldelim = NOCHAR;
594 						rdelim = NOCHAR;
595 					} else {
596 						ldelim = c1;
597 						rdelim = c2;
598 					}
599 				}
600 			dflg = 0;
601 		}
602 
603 		if (c != '\n')
604 			while (C1 != '\n') {
605 				if (chars[c] == PUNCT)
606 					last = c;
607 				else if (c != ' ')
608 					last = 0;
609 			}
610 	}
611 }
612 
613 /* skip over a complete backslash construction */
614 void
615 backsl(void)
616 {
617 	int bdelim;
618 
619 sw:
620 	switch (C) {
621 	case '"':
622 		SKIP;
623 		return;
624 
625 	case 's':
626 		if (C == '\\')
627 			backsl();
628 		else {
629 			while (C >= '0' && c <= '9')
630 				;	/* nothing */
631 			ungetc(c, infile);
632 			c = '0';
633 		}
634 		--lp;
635 		return;
636 
637 	case 'f':
638 	case 'n':
639 	case '*':
640 		if (C != '(')
641 			return;
642 
643 	case '(':
644 		if (msflag) {
645 			if (C == 'e') {
646 				if (C == 'm') {
647 					*lp = '-';
648 					return;
649 				}
650 			}
651 			else if (c != '\n')
652 				C;
653 			return;
654 		}
655 		if (C != '\n')
656 			C;
657 		return;
658 
659 	case '$':
660 		C;	/* discard argument number */
661 		return;
662 
663 	case 'b':
664 	case 'x':
665 	case 'v':
666 	case 'h':
667 	case 'w':
668 	case 'o':
669 	case 'l':
670 	case 'L':
671 		if ((bdelim = C) == '\n')
672 			return;
673 		while (C != '\n' && c != bdelim)
674 			if (c == '\\')
675 				backsl();
676 		return;
677 
678 	case '\\':
679 		if (inmacro)
680 			goto sw;
681 
682 	default:
683 		return;
684 	}
685 }
686 
687 void
688 sce(void)
689 {
690 	char *ap;
691 	int n, i;
692 	char a[10];
693 
694 	for (ap = a; C != '\n'; ap++) {
695 		*ap = c;
696 		if (ap == &a[9]) {
697 			SKIP;
698 			ap = a;
699 			break;
700 		}
701 	}
702 	if (ap != a)
703 		n = atoi(a);
704 	else
705 		n = 1;
706 	for (i = 0; i < n;) {
707 		if (C == '.') {
708 			if (C == 'c') {
709 				if (C == 'e') {
710 					while (C == ' ')
711 						;	/* nothing */
712 					if (c == '0') {
713 						SKIP;
714 						break;
715 					} else
716 						SKIP;
717 				}
718 				else
719 					SKIP;
720 			} else if (c == 'P' || C == 'P') {
721 				if (c != '\n')
722 					SKIP;
723 				break;
724 			} else if (c != '\n')
725 				SKIP;
726 		} else {
727 			SKIP;
728 			i++;
729 		}
730 	}
731 }
732 
733 void
734 refer(int c1)
735 {
736 	int c2;
737 
738 	if (c1 != '\n')
739 		SKIP;
740 
741 	for (c2 = -1;;) {
742 		if (C != '.')
743 			SKIP;
744 		else {
745 			if (C != ']')
746 				SKIP;
747 			else {
748 				while (C != '\n')
749 					c2 = c;
750 				if (c2 != -1 && chars[c2] == PUNCT)
751 					putchar(c2);
752 				return;
753 			}
754 		}
755 	}
756 }
757 
758 void
759 inpic(void)
760 {
761 	int c1;
762 	char *p1;
763 
764 	SKIP;
765 	p1 = line;
766 	c = '\n';
767 	for (;;) {
768 		c1 = c;
769 		if (C == '.' && c1 == '\n') {
770 			if (C != 'P') {
771 				if (c == '\n')
772 					continue;
773 				else {
774 					SKIP;
775 					c = '\n';
776 					continue;
777 				}
778 			}
779 			if (C != 'E') {
780 				if (c == '\n')
781 					continue;
782 				else {
783 					SKIP;
784 					c = '\n';
785 					continue;
786 				}
787 			}
788 			SKIP;
789 			return;
790 		}
791 		else if (c == '\"') {
792 			while (C != '\"') {
793 				if (c == '\\') {
794 					if (C == '\"')
795 						continue;
796 					ungetc(c, infile);
797 					backsl();
798 				} else
799 					*p1++ = c;
800 			}
801 			*p1++ = ' ';
802 		}
803 		else if (c == '\n' && p1 != line) {
804 			*p1 = '\0';
805 			if (wordflag)
806 				msputwords(NO);
807 			else {
808 				puts(line);
809 				putchar('\n');
810 			}
811 			p1 = line;
812 		}
813 	}
814 }
815 
816 #ifdef DEBUG
817 int
818 _C1(void)
819 {
820 
821 	return(C1get);
822 }
823 
824 int
825 _C(void)
826 {
827 
828 	return(Cget);
829 }
830 #endif /* DEBUG */
831 
832 /*
833  *	Put out a macro line, using ms and mm conventions.
834  */
835 void
836 msputmac(char *s, int constant)
837 {
838 	char *t;
839 	int found;
840 	int last;
841 
842 	last = 0;
843 	found = 0;
844 	if (wordflag) {
845 		msputwords(YES);
846 		return;
847 	}
848 	while (*s) {
849 		while (*s == ' ' || *s == '\t')
850 			putchar(*s++);
851 		for (t = s ; *t != ' ' && *t != '\t' && *t != '\0' ; ++t)
852 			;	/* nothing */
853 		if (*s == '\"')
854 			s++;
855 		if (t > s + constant && chars[(unsigned char)s[0]] == LETTER &&
856 		    chars[(unsigned char)s[1]] == LETTER) {
857 			while (s < t)
858 				if (*s == '\"')
859 					s++;
860 				else
861 					putchar(*s++);
862 			last = *(t-1);
863 			found++;
864 		} else if (found && chars[(unsigned char)s[0]] == PUNCT &&
865 		    s[1] == '\0') {
866 			putchar(*s++);
867 		} else {
868 			last = *(t - 1);
869 			s = t;
870 		}
871 	}
872 	putchar('\n');
873 	if (msflag && chars[last] == PUNCT) {
874 		putchar(last);
875 		putchar('\n');
876 	}
877 }
878 
879 /*
880  *	put out words (for the -w option) with ms and mm conventions
881  */
882 void
883 msputwords(int macline)
884 {
885 	char *p, *p1;
886 	int i, nlet;
887 
888 	for (p1 = line;;) {
889 		/*
890 		 *	skip initial specials ampersands and apostrophes
891 		 */
892 		while (chars[(unsigned char)*p1] < DIGIT)
893 			if (*p1++ == '\0')
894 				return;
895 		nlet = 0;
896 		for (p = p1 ; (i = chars[(unsigned char)*p]) != SPECIAL ; ++p)
897 			if (i == LETTER)
898 				++nlet;
899 
900 		if (nlet > 1 && chars[(unsigned char)p1[0]] == LETTER) {
901 			/*
902 			 *	delete trailing ampersands and apostrophes
903 			 */
904 			while ((i = chars[(unsigned char)p[-1]]) == PUNCT ||
905 			    i == APOS )
906 				--p;
907 			while (p1 < p)
908 				putchar(*p1++);
909 			putchar('\n');
910 		} else {
911 			p1 = p;
912 		}
913 	}
914 }
915 
916 /*
917  *	put out a macro using the me conventions
918  */
919 #define SKIPBLANK(cp)	while (*cp == ' ' || *cp == '\t') { cp++; }
920 #define SKIPNONBLANK(cp) while (*cp !=' ' && *cp !='\cp' && *cp !='\0') { cp++; }
921 
922 void
923 meputmac(char *cp, int constant)
924 {
925 	char	*np;
926 	int	found;
927 	int	argno;
928 	int	last;
929 	int	inquote;
930 
931 	last = 0;
932 	found = 0;
933 	if (wordflag) {
934 		meputwords(YES);
935 		return;
936 	}
937 	for (argno = 0; *cp; argno++) {
938 		SKIPBLANK(cp);
939 		inquote = (*cp == '"');
940 		if (inquote)
941 			cp++;
942 		for (np = cp; *np; np++) {
943 			switch (*np) {
944 			case '\n':
945 			case '\0':
946 				break;
947 
948 			case '\t':
949 			case ' ':
950 				if (inquote)
951 					continue;
952 				else
953 					goto endarg;
954 
955 			case '"':
956 				if (inquote && np[1] == '"') {
957 					memmove(np, np + 1, strlen(np));
958 					np++;
959 					continue;
960 				} else {
961 					*np = ' '; 	/* bye bye " */
962 					goto endarg;
963 				}
964 
965 			default:
966 				continue;
967 			}
968 		}
969 		endarg: ;
970 		/*
971 		 *	cp points at the first char in the arg
972 		 *	np points one beyond the last char in the arg
973 		 */
974 		if ((argconcat == 0) || (argconcat != argno))
975 			putchar(' ');
976 #ifdef FULLDEBUG
977 		{
978 			char	*p;
979 			printf("[%d,%d: ", argno, np - cp);
980 			for (p = cp; p < np; p++) {
981 				putchar(*p);
982 			}
983 			printf("]");
984 		}
985 #endif /* FULLDEBUG */
986 		/*
987 		 *	Determine if the argument merits being printed
988 		 *
989 		 *	constant is the cut off point below which something
990 		 *	is not a word.
991 		 */
992 		if (((np - cp) > constant) &&
993 		    (inquote || (chars[(unsigned char)cp[0]] == LETTER))) {
994 			for (cp = cp; cp < np; cp++)
995 				putchar(*cp);
996 			last = np[-1];
997 			found++;
998 		} else if (found && (np - cp == 1) &&
999 		    chars[(unsigned char)*cp] == PUNCT) {
1000 			putchar(*cp);
1001 		} else {
1002 			last = np[-1];
1003 		}
1004 		cp = np;
1005 	}
1006 	if (msflag && chars[last] == PUNCT)
1007 		putchar(last);
1008 	putchar('\n');
1009 }
1010 
1011 /*
1012  *	put out words (for the -w option) with ms and mm conventions
1013  */
1014 void
1015 meputwords(int macline)
1016 {
1017 
1018 	msputwords(macline);
1019 }
1020 
1021 /*
1022  *
1023  *	Skip over a nested set of macros
1024  *
1025  *	Possible arguments to noblock are:
1026  *
1027  *	fi	end of unfilled text
1028  *	PE	pic ending
1029  *	DE	display ending
1030  *
1031  *	for ms and mm only:
1032  *		KE	keep ending
1033  *
1034  *		NE	undocumented match to NS (for mm?)
1035  *		LE	mm only: matches RL or *L (for lists)
1036  *
1037  *	for me:
1038  *		([lqbzcdf]
1039  */
1040 void
1041 noblock(char a1, char a2)
1042 {
1043 	int c1,c2;
1044 	int eqnf;
1045 	int lct;
1046 
1047 	lct = 0;
1048 	eqnf = 1;
1049 	SKIP;
1050 	for (;;) {
1051 		while (C != '.')
1052 			if (c == '\n')
1053 				continue;
1054 			else
1055 				SKIP;
1056 		if ((c1 = C) == '\n')
1057 			continue;
1058 		if ((c2 = C) == '\n')
1059 			continue;
1060 		if (c1 == a1 && c2 == a2) {
1061 			SKIP;
1062 			if (lct != 0) {
1063 				lct--;
1064 				continue;
1065 			}
1066 			if (eqnf)
1067 				putchar('.');
1068 			putchar('\n');
1069 			return;
1070 		} else if (a1 == 'L' && c2 == 'L') {
1071 			lct++;
1072 			SKIP;
1073 		}
1074 		/*
1075 		 *	equations (EQ) nested within a display
1076 		 */
1077 		else if (c1 == 'E' && c2 == 'Q') {
1078 			if ((mac == ME && a1 == ')')
1079 			    || (mac != ME && a1 == 'D')) {
1080 				eqn();
1081 				eqnf=0;
1082 			}
1083 		}
1084 		/*
1085 		 *	turning on filling is done by the paragraphing
1086 		 *	macros
1087 		 */
1088 		else if (a1 == 'f') {	/* .fi */
1089 			if  ((mac == ME && (c2 == 'h' || c2 == 'p'))
1090 			    || (mac != ME && (c1 == 'P' || c2 == 'P'))) {
1091 				SKIP;
1092 				return;
1093 			}
1094 		} else {
1095 			SKIP;
1096 		}
1097 	}
1098 }
1099 
1100 int
1101 EQ(void)
1102 {
1103 
1104 	eqn();
1105 	return(0);
1106 }
1107 
1108 int
1109 domacro(void)
1110 {
1111 
1112 	macro();
1113 	return(0);
1114 }
1115 
1116 int
1117 PS(void)
1118 {
1119 
1120 	for (C; c == ' ' || c == '\t'; C)
1121 		;	/* nothing */
1122 
1123 	if (c == '<') {		/* ".PS < file" -- don't expect a .PE */
1124 		SKIP;
1125 		return(0);
1126 	}
1127 	if (!msflag)
1128 		inpic();
1129 	else
1130 		noblock('P', 'E');
1131 	return(0);
1132 }
1133 
1134 int
1135 skip(void)
1136 {
1137 
1138 	SKIP;
1139 	return(0);
1140 }
1141 
1142 int
1143 intbl(void)
1144 {
1145 
1146 	if (msflag)
1147 		stbl();
1148 	else
1149 		tbl();
1150 	return(0);
1151 }
1152 
1153 int
1154 outtbl(void)
1155 {
1156 
1157 	intable = NO;
1158 	return(0);
1159 }
1160 
1161 int
1162 so(void)
1163 {
1164 
1165 	if (!iflag) {
1166 		getfname();
1167 		if (fname[0]) {
1168 			if (++filesp - &files[0] > MAXFILES)
1169 				err(1, "too many nested files (max %d)",
1170 				    MAXFILES);
1171 			infile = *filesp = opn(fname);
1172 		}
1173 	}
1174 	return(0);
1175 }
1176 
1177 int
1178 nx(void)
1179 {
1180 
1181 	if (!iflag) {
1182 		getfname();
1183 		if (fname[0] == '\0')
1184 			exit(0);
1185 		if (infile != stdin)
1186 			fclose(infile);
1187 		infile = *filesp = opn(fname);
1188 	}
1189 	return(0);
1190 }
1191 
1192 int
1193 skiptocom(void)
1194 {
1195 
1196 	SKIP_TO_COM;
1197 	return(COMX);
1198 }
1199 
1200 int
1201 PP(pacmac c12)
1202 {
1203 	int c1, c2;
1204 
1205 	frommac(c12, c1, c2);
1206 	printf(".%c%c", c1, c2);
1207 	while (C != '\n')
1208 		putchar(c);
1209 	putchar('\n');
1210 	return(0);
1211 }
1212 
1213 int
1214 AU(void)
1215 {
1216 
1217 	if (mac == MM)
1218 		return(0);
1219 	SKIP_TO_COM;
1220 	return(COMX);
1221 }
1222 
1223 int
1224 SH(pacmac c12)
1225 {
1226 	int c1, c2;
1227 
1228 	frommac(c12, c1, c2);
1229 
1230 	if (parag) {
1231 		printf(".%c%c", c1, c2);
1232 		while (C != '\n')
1233 			putchar(c);
1234 		putchar(c);
1235 		putchar('!');
1236 		for (;;) {
1237 			while (C != '\n')
1238 				putchar(c);
1239 			putchar('\n');
1240 			if (C == '.')
1241 				return(COM);
1242 			putchar('!');
1243 			putchar(c);
1244 		}
1245 		/*NOTREACHED*/
1246 	} else {
1247 		SKIP_TO_COM;
1248 		return(COMX);
1249 	}
1250 }
1251 
1252 int
1253 UX(void)
1254 {
1255 
1256 	if (wordflag)
1257 		printf("UNIX\n");
1258 	else
1259 		printf("UNIX ");
1260 	return(0);
1261 }
1262 
1263 int
1264 MMHU(pacmac c12)
1265 {
1266 	int c1, c2;
1267 
1268 	frommac(c12, c1, c2);
1269 	if (parag) {
1270 		printf(".%c%c", c1, c2);
1271 		while (C != '\n')
1272 			putchar(c);
1273 		putchar('\n');
1274 	} else {
1275 		SKIP;
1276 	}
1277 	return(0);
1278 }
1279 
1280 int
1281 mesnblock(pacmac c12)
1282 {
1283 	int c1, c2;
1284 
1285 	frommac(c12, c1, c2);
1286 	noblock(')', c2);
1287 	return(0);
1288 }
1289 
1290 int
1291 mssnblock(pacmac c12)
1292 {
1293 	int c1, c2;
1294 
1295 	frommac(c12, c1, c2);
1296 	noblock(c1, 'E');
1297 	return(0);
1298 }
1299 
1300 int
1301 nf(void)
1302 {
1303 
1304 	noblock('f', 'i');
1305 	return(0);
1306 }
1307 
1308 int
1309 ce(void)
1310 {
1311 
1312 	sce();
1313 	return(0);
1314 }
1315 
1316 int
1317 meip(pacmac c12)
1318 {
1319 
1320 	if (parag)
1321 		mepp(c12);
1322 	else if (wordflag)	/* save the tag */
1323 		regline(meputmac, ONE);
1324 	else
1325 		SKIP;
1326 	return(0);
1327 }
1328 
1329 /*
1330  *	only called for -me .pp or .sh, when parag is on
1331  */
1332 int
1333 mepp(pacmac c12)
1334 {
1335 
1336 	PP(c12);		/* eats the line */
1337 	return(0);
1338 }
1339 
1340 /*
1341  *	Start of a section heading; output the section name if doing words
1342  */
1343 int
1344 mesh(pacmac c12)
1345 {
1346 
1347 	if (parag)
1348 		mepp(c12);
1349 	else if (wordflag)
1350 		defcomline(c12);
1351 	else
1352 		SKIP;
1353 	return(0);
1354 }
1355 
1356 /*
1357  *	process a font setting
1358  */
1359 int
1360 mefont(pacmac c12)
1361 {
1362 
1363 	argconcat = 1;
1364 	defcomline(c12);
1365 	argconcat = 0;
1366 	return(0);
1367 }
1368 
1369 int
1370 manfont(pacmac c12)
1371 {
1372 
1373 	return(mefont(c12));
1374 }
1375 
1376 int
1377 manpp(pacmac c12)
1378 {
1379 
1380 	return(mepp(c12));
1381 }
1382 
1383 void
1384 defcomline(pacmac c12)
1385 {
1386 	int c1, c2;
1387 
1388 	frommac(c12, c1, c2);
1389 	if (msflag && mac == MM && c2 == 'L') {
1390 		if (disp || c1 == 'R') {
1391 			noblock('L', 'E');
1392 		} else {
1393 			SKIP;
1394 			putchar('.');
1395 		}
1396 	}
1397 	else if (c1 == '.' && c2 == '.') {
1398 		if (msflag) {
1399 			SKIP;
1400 			return;
1401 		}
1402 		while (C == '.')
1403 			/*VOID*/;
1404 	}
1405 	++inmacro;
1406 	/*
1407 	 *	Process the arguments to the macro
1408 	 */
1409 	switch (mac) {
1410 	default:
1411 	case MM:
1412 	case MS:
1413 		if (c1 <= 'Z' && msflag)
1414 			regline(msputmac, ONE);
1415 		else
1416 			regline(msputmac, TWO);
1417 		break;
1418 	case ME:
1419 		regline(meputmac, ONE);
1420 		break;
1421 	}
1422 	--inmacro;
1423 }
1424 
1425 void
1426 comline(void)
1427 {
1428 	int	c1;
1429 	int	c2;
1430 	pacmac	c12;
1431 	int	mid;
1432 	int	lb, ub;
1433 	int	hit;
1434 	static	int	tabsize = 0;
1435 	static	struct	mactab	*mactab = (struct mactab *)0;
1436 	struct	mactab	*mp;
1437 
1438 	if (mactab == 0)
1439 		 buildtab(&mactab, &tabsize);
1440 com:
1441 	while (C == ' ' || c == '\t')
1442 		;
1443 comx:
1444 	if ((c1 = c) == '\n')
1445 		return;
1446 	c2 = C;
1447 	if (c1 == '.' && c2 != '.')
1448 		inmacro = NO;
1449 	if (msflag && c1 == '[') {
1450 		refer(c2);
1451 		return;
1452 	}
1453 	if (parag && mac==MM && c1 == 'P' && c2 == '\n') {
1454 		printf(".P\n");
1455 		return;
1456 	}
1457 	if (c2 == '\n')
1458 		return;
1459 	/*
1460 	 *	Single letter macro
1461 	 */
1462 	if (mac == ME && (c2 == ' ' || c2 == '\t') )
1463 		c2 = ' ';
1464 	c12 = tomac(c1, c2);
1465 	/*
1466 	 *	binary search through the table of macros
1467 	 */
1468 	lb = 0;
1469 	ub = tabsize - 1;
1470 	while (lb <= ub) {
1471 		mid = (ub + lb) / 2;
1472 		mp = &mactab[mid];
1473 		if (mp->macname < c12)
1474 			lb = mid + 1;
1475 		else if (mp->macname > c12)
1476 			ub = mid - 1;
1477 		else {
1478 			hit = 1;
1479 #ifdef FULLDEBUG
1480 			printf("preliminary hit macro %c%c ", c1, c2);
1481 #endif /* FULLDEBUG */
1482 			switch (mp->condition) {
1483 			case NONE:
1484 				hit = YES;
1485 				break;
1486 			case FNEST:
1487 				hit = (filesp == files);
1488 				break;
1489 			case NOMAC:
1490 				hit = !inmacro;
1491 				break;
1492 			case MAC:
1493 				hit = inmacro;
1494 				break;
1495 			case PARAG:
1496 				hit = parag;
1497 				break;
1498 			case NBLK:
1499 				hit = !keepblock;
1500 				break;
1501 			default:
1502 				hit = 0;
1503 			}
1504 
1505 			if (hit) {
1506 #ifdef FULLDEBUG
1507 				printf("MATCH\n");
1508 #endif /* FULLDEBUG */
1509 				switch ((*(mp->func))(c12)) {
1510 				default:
1511 					return;
1512 				case COMX:
1513 					goto comx;
1514 				case COM:
1515 					goto com;
1516 				}
1517 			}
1518 #ifdef FULLDEBUG
1519 			printf("FAIL\n");
1520 #endif /* FULLDEBUG */
1521 			break;
1522 		}
1523 	}
1524 	defcomline(c12);
1525 }
1526 
1527 int
1528 macsort(const void *p1, const void *p2)
1529 {
1530 	struct mactab *t1 = (struct mactab *)p1;
1531 	struct mactab *t2 = (struct mactab *)p2;
1532 
1533 	return(t1->macname - t2->macname);
1534 }
1535 
1536 int
1537 sizetab(struct mactab *mp)
1538 {
1539 	int i;
1540 
1541 	i = 0;
1542 	if (mp) {
1543 		for (; mp->macname; mp++, i++)
1544 			/*VOID*/ ;
1545 	}
1546 	return(i);
1547 }
1548 
1549 struct mactab *
1550 macfill(struct mactab *dst, struct mactab *src)
1551 {
1552 
1553 	if (src) {
1554 		while (src->macname)
1555 			*dst++ = *src++;
1556 	}
1557 	return(dst);
1558 }
1559 
1560 __dead void
1561 usage(void)
1562 {
1563 	extern char *__progname;
1564 
1565 	fprintf(stderr, "usage: %s [-ikpw] [-m a | e | l | m | s] [file ...]\n", __progname);
1566 	exit(1);
1567 }
1568 
1569 void
1570 buildtab(struct mactab **r_back, int *r_size)
1571 {
1572 	int	size;
1573 	struct	mactab	*p, *p1, *p2;
1574 	struct	mactab	*back;
1575 
1576 	size = sizetab(troffmactab) + sizetab(ppmactab);
1577 	p1 = p2 = NULL;
1578 	if (msflag) {
1579 		switch (mac) {
1580 		case ME:
1581 			p1 = memactab;
1582 			break;
1583 		case MM:
1584 			p1 = msmactab;
1585 			p2 = mmmactab;
1586 			break;
1587 		case MS:
1588 			p1 = msmactab;
1589 			break;
1590 		case MA:
1591 			p1 = manmactab;
1592 			break;
1593 		default:
1594 			break;
1595 		}
1596 	}
1597 	size += sizetab(p1);
1598 	size += sizetab(p2);
1599 	back = (struct mactab *)calloc(size+2, sizeof(struct mactab));
1600 	if (back == NULL)
1601 		err(1, NULL);
1602 
1603 	p = macfill(back, troffmactab);
1604 	p = macfill(p, ppmactab);
1605 	p = macfill(p, p1);
1606 	p = macfill(p, p2);
1607 
1608 	qsort(back, size, sizeof(struct mactab), macsort);
1609 	*r_size = size;
1610 	*r_back = back;
1611 }
1612 
1613 /*
1614  *	troff commands
1615  */
1616 struct	mactab	troffmactab[] = {
1617 	M(NONE,		'\\','"',	skip),	/* comment */
1618 	M(NOMAC,	'd','e',	domacro),	/* define */
1619 	M(NOMAC,	'i','g',	domacro),	/* ignore till .. */
1620 	M(NOMAC,	'a','m',	domacro),	/* append macro */
1621 	M(NBLK,		'n','f',	nf),	/* filled */
1622 	M(NBLK,		'c','e',	ce),	/* centered */
1623 
1624 	M(NONE,		's','o',	so),	/* source a file */
1625 	M(NONE,		'n','x',	nx),	/* go to next file */
1626 
1627 	M(NONE,		't','m',	skip),	/* print string on tty */
1628 	M(NONE,		'h','w',	skip),	/* exception hyphen words */
1629 	M(NONE,		0,0,		0)
1630 };
1631 
1632 /*
1633  *	Preprocessor output
1634  */
1635 struct	mactab	ppmactab[] = {
1636 	M(FNEST,	'E','Q',	EQ),	/* equation starting */
1637 	M(FNEST,	'T','S',	intbl),	/* table starting */
1638 	M(FNEST,	'T','C',	intbl),	/* alternative table? */
1639 	M(FNEST,	'T','&',	intbl),	/* table reformatting */
1640 	M(NONE,		'T','E',	outtbl),/* table ending */
1641 	M(NONE,		'P','S',	PS),	/* picture starting */
1642 	M(NONE,		0,0,		0)
1643 };
1644 
1645 /*
1646  *	Particular to ms and mm
1647  */
1648 struct	mactab	msmactab[] = {
1649 	M(NONE,		'T','L',	skiptocom),	/* title follows */
1650 	M(NONE,		'F','S',	skiptocom),	/* start footnote */
1651 	M(NONE,		'O','K',	skiptocom),	/* Other kws */
1652 
1653 	M(NONE,		'N','R',	skip),	/* undocumented */
1654 	M(NONE,		'N','D',	skip),	/* use supplied date */
1655 
1656 	M(PARAG,	'P','P',	PP),	/* begin parag */
1657 	M(PARAG,	'I','P',	PP),	/* begin indent parag, tag x */
1658 	M(PARAG,	'L','P',	PP),	/* left blocked parag */
1659 
1660 	M(NONE,		'A','U',	AU),	/* author */
1661 	M(NONE,		'A','I',	AU),	/* authors institution */
1662 
1663 	M(NONE,		'S','H',	SH),	/* section heading */
1664 	M(NONE,		'S','N',	SH),	/* undocumented */
1665 	M(NONE,		'U','X',	UX),	/* unix */
1666 
1667 	M(NBLK,		'D','S',	mssnblock),	/* start display text */
1668 	M(NBLK,		'K','S',	mssnblock),	/* start keep */
1669 	M(NBLK,		'K','F',	mssnblock),	/* start float keep */
1670 	M(NONE,		0,0,		0)
1671 };
1672 
1673 struct	mactab	mmmactab[] = {
1674 	M(NONE,		'H',' ',	MMHU),	/* -mm ? */
1675 	M(NONE,		'H','U',	MMHU),	/* -mm ? */
1676 	M(PARAG,	'P',' ',	PP),	/* paragraph for -mm */
1677 	M(NBLK,		'N','S',	mssnblock),	/* undocumented */
1678 	M(NONE,		0,0,		0)
1679 };
1680 
1681 struct	mactab	memactab[] = {
1682 	M(PARAG,	'p','p',	mepp),
1683 	M(PARAG,	'l','p',	mepp),
1684 	M(PARAG,	'n','p',	mepp),
1685 	M(NONE,		'i','p',	meip),
1686 
1687 	M(NONE,		's','h',	mesh),
1688 	M(NONE,		'u','h',	mesh),
1689 
1690 	M(NBLK,		'(','l',	mesnblock),
1691 	M(NBLK,		'(','q',	mesnblock),
1692 	M(NBLK,		'(','b',	mesnblock),
1693 	M(NBLK,		'(','z',	mesnblock),
1694 	M(NBLK,		'(','c',	mesnblock),
1695 
1696 	M(NBLK,		'(','d',	mesnblock),
1697 	M(NBLK,		'(','f',	mesnblock),
1698 	M(NBLK,		'(','x',	mesnblock),
1699 
1700 	M(NONE,		'r',' ',	mefont),
1701 	M(NONE,		'i',' ',	mefont),
1702 	M(NONE,		'b',' ',	mefont),
1703 	M(NONE,		'u',' ',	mefont),
1704 	M(NONE,		'q',' ',	mefont),
1705 	M(NONE,		'r','b',	mefont),
1706 	M(NONE,		'b','i',	mefont),
1707 	M(NONE,		'b','x',	mefont),
1708 	M(NONE,		0,0,		0)
1709 };
1710 
1711 struct	mactab	manmactab[] = {
1712 	M(PARAG,	'B','I',	manfont),
1713 	M(PARAG,	'B','R',	manfont),
1714 	M(PARAG,	'I','B',	manfont),
1715 	M(PARAG,	'I','R',	manfont),
1716 	M(PARAG,	'R','B',	manfont),
1717 	M(PARAG,	'R','I',	manfont),
1718 
1719 	M(PARAG,	'P','P',	manpp),
1720 	M(PARAG,	'L','P',	manpp),
1721 	M(PARAG,	'H','P',	manpp),
1722 	M(NONE,		0,0,		0)
1723 };
1724