xref: /minix/minix/commands/cawf/pass2.c (revision 433d6423)
1 /*
2  *	pass2.c - cawf(1) pass 2 function
3  */
4 
5 /*
6  *	Copyright (c) 1991 Purdue University Research Foundation,
7  *	West Lafayette, Indiana 47907.  All rights reserved.
8  *
9  *	Written by Victor A. Abell <abe@mace.cc.purdue.edu>,  Purdue
10  *	University Computing Center.  Not derived from licensed software;
11  *	derived from awf(1) by Henry Spencer of the University of Toronto.
12  *
13  *	Permission is granted to anyone to use this software for any
14  *	purpose on any computer system, and to alter it and redistribute
15  *	it freely, subject to the following restrictions:
16  *
17  *	1. The author is not responsible for any consequences of use of
18  *	   this software, even if they arise from flaws in it.
19  *
20  *	2. The origin of this software must not be misrepresented, either
21  *	   by explicit claim or by omission.  Credits must appear in the
22  *	   documentation.
23  *
24  *	3. Altered versions must be plainly marked as such, and must not
25  *	   be misrepresented as being the original software.  Credits must
26  *	   appear in the documentation.
27  *
28  *	4. This notice may not be removed or altered.
29  */
30 
31 #include "cawf.h"
32 #include <ctype.h>
33 
34 /*
35  * Pass2(line) - process the nroff requests in a line and break
36  *		 text into words for pass 3
37  */
38 
39 void
40 Pass2(line)
41 	unsigned char *line;
42 {
43 	int brk;			/* request break status */
44 	unsigned char buf[MAXLINE];	/* working buffer */
45 	unsigned char c;		/* character buffer */
46 	double d;			/* temporary double */
47 	double exscale;			/* expression scaling factor */
48 	double expr[MAXEXP];            /* expressions */
49 	unsigned char exsign[MAXEXP];	/* expression signs */
50 	int i, j;			/* temporary indexes */
51 	int inword;			/* word processing status */
52 	int nexpr;			/* number of expressions */
53 	unsigned char nm[4], nm1[4];	/* names */
54 	int nsp;			/* number of spaces */
55 	unsigned char op;		/* expression term operator */
56 	unsigned char opstack[MAXSP];	/* expression operation stack */
57 	unsigned char period;		/* end of word status */
58 	unsigned char *s1, *s2, *s3;	/* temporary string pointers */
59 	double sexpr[MAXEXP];           /* signed expressions */
60 	int sp;				/* expression stack pointer */
61 	unsigned char ssign;		/* expression's starting sign */
62 	int tabpos;			/* tab position */
63 	double tscale;			/* term scaling factor */
64 	double tval;			/* term value */
65 	double val;			/* term value */
66 	double valstack[MAXSP];		/* expression value stack */
67 	unsigned char xbuf[MAXLINE];	/* expansion buffer */
68 
69 	if (line == NULL) {
70     /*
71      * End of macro expansion.
72      */
73 		Pass3(DOBREAK, (unsigned char *)"need", NULL, 999);
74 		return;
75 	}
76     /*
77      * Adjust line number.
78      */
79 	if (Lockil == 0)
80 		P2il++;
81     /*
82      * Empty line - "^[ \t]*$" or "^\\\"".
83      */
84 	if (regexec(Pat[6].pat, line)
85 	||  strncmp((char *)line, "\\\"", 2) == 0) {
86 		Pass3(DOBREAK, (unsigned char *)"space", NULL, 0);
87 		return;
88 	}
89     /*
90      * Line begins with white space.
91      */
92 	if (*line == ' ' || *line == '\t') {
93 		Pass3(DOBREAK, (unsigned char *)"flush", NULL, 0);
94 		Pass3(0, (unsigned char *)"", NULL, 0);
95 	}
96 	if (*line != '.' && *line != '\'') {
97     /*
98      * Line contains text (not an nroff request).
99      */
100 		if (Font[0] == 'R' && Backc == 0 && Aftnxt == NULL
101 		&&  regexec(Pat[7].pat, line) == 0) {
102 		    /*
103 		     * The font is Roman, there is no "\\c" or "after next"
104 		     * trap pending and and the line has no '\\', '\t', '-',
105 		     * or "  "  (regular expression "\\|\t|-|  ").
106 		     *
107 		     * Output each word of the line as "<length> <word>".
108 		     */
109 			for (s1 = line;;) {
110 				while (*s1 && *s1 == ' ')
111 					s1++;
112 				if (*s1 == '\0')
113 					break;
114 				for (s2 = s1, s3 = buf; *s2 && *s2 != ' ';)
115 				    *s3++ = Trtbl[(int)*s2++];
116 				*s3 = '\0';
117 				Pass3((s2 - s1), buf, NULL, 0);
118 				s1 = *s2 ? ++s2 : s2;
119 			}
120 		    /*
121 		     * Line terminates with punctuation and optional
122 		     * bracketing (regular expression "[.!?:][\])'\"*]*$").
123 		     */
124 			if (regexec(Pat[8].pat, line))
125 				Pass3(NOBREAK, (unsigned char *)"gap", NULL, 2);
126 			if (Centering > 0) {
127 				Pass3(DOBREAK,(unsigned char *)"center", NULL,
128 					0);
129 				Centering--;
130 			} else if (Fill == 0)
131 				Pass3(DOBREAK, (unsigned char *)"flush", NULL,
132 					0);
133 			return;
134 		}
135 	    /*
136 	     * Line must be scanned a character at a time.
137 	     */
138 		inword = nsp = tabpos = 0;
139 		period = '\0';
140 		for (s1 = line;; s1++) {
141 		    /*
142 		     * Space or TAB causes state transition.
143 		     */
144 			if (*s1 == '\0' || *s1 == ' ' || *s1 == '\t') {
145 				if (inword) {
146 					if (!Backc) {
147 						Endword();
148 						Pass3(Wordl, Word, NULL, 0);
149 						if (Uhyph) {
150 						  Pass3(NOBREAK,
151 						    (unsigned char *)"nohyphen",
152 						    NULL, 0);
153 						}
154 					}
155 					inword = 0;
156 					nsp = 0;
157 				}
158 				if (*s1 == '\0')
159 					break;
160 			} else {
161 				if (inword == 0) {
162 					if (Backc == 0) {
163 						Wordl = Wordx = 0;
164 						Uhyph = 0;
165 					}
166 					Backc = 0;
167 					inword = 1;
168 					if (nsp > 1) {
169 						Pass3(NOBREAK,
170 						    (unsigned char *)"gap",
171 						    NULL, nsp);
172 					}
173 				}
174 			}
175 		    /*
176 		     * Process a character.
177 		     */
178 			switch (*s1) {
179 		    /*
180 		     * Space
181 		     */
182 	     		case ' ':
183 				nsp++;
184 				period = '\0';
185 				break;
186 		    /*
187 		     * TAB
188 		     */
189 	     		case '\t':
190 				tabpos++;
191 				if (tabpos <= Ntabs) {
192 					Pass3(NOBREAK,
193 					    (unsigned char *)"tabto", NULL,
194 					    Tabs[tabpos-1]);
195 				}
196 				nsp = 0;
197 				period = '\0';
198 				break;
199 		    /*
200 		     * Hyphen if word is being assembled
201 		     */
202 			case '-':
203 				if (Wordl <= 0)
204 				    goto ordinary_char;
205 				if ((i = Findhy(NULL, 0, 0)) < 0) {
206 				    Error(WARN, LINE, " no hyphen for font ",
207 					(char *)Font);
208 				    return;
209 				}
210 				Endword();
211 				Pass3(Wordl, Word, NULL, Hychar[i].len);
212 				Pass3(NOBREAK, (unsigned char *)"userhyphen",
213 				    Hychar[i].str, Hychar[i].len);
214 				Wordl = Wordx = 0;
215 				period = '\0';
216 				Uhyph = 1;
217 				break;
218 		    /*
219 		     * Backslash
220 		     */
221 			case '\\':
222 				s1++;
223 				switch(*s1) {
224 			    /*
225 			     * Comment - "\\\""
226 			     */
227 				case '"':
228 					while (*(s1+1))
229 						s1++;
230 					break;
231 			    /*
232 			     * Change font - "\\fN"
233 			     */
234 				case 'f':
235 					s1 = Asmcode(&s1, nm);
236 					if (nm[0] == 'P') {
237 					    Font[0] = Prevfont;
238 					    break;
239 					}
240 					for (i = 0; Fcode[i].nm; i++) {
241 					    if (Fcode[i].nm == nm[0])
242 						break;
243 					}
244 					if (Fcode[i].nm == '\0'
245 					||  nm[1] != '\0') {
246 					    Error(WARN, LINE, " unknown font ",
247 					    	(char *)nm);
248 					    break;
249 					}
250 					if (Fcode[i].status != '1') {
251 					    Error(WARN, LINE,
252 						" font undefined ", (char *)nm);
253 					    break;
254 					} else {
255 					    Prevfont = Font[0];
256 					    Font[0] = nm[0];
257 					}
258 					break;
259 			    /*
260 			     * Positive horizontal motion - "\\h\\n(NN" or
261 			     * "\\h\\nN"
262 			     */
263 				case 'h':
264 					if (s1[1] != '\\' || s1[2] != 'n') {
265 					    Error(WARN, LINE,
266 						" no \\n after \\h", NULL);
267 					    break;
268 					}
269 					s1 +=2;
270 					s1 = Asmcode(&s1, nm);
271 					if ((i = Findnum(nm, 0, 0)) < 0)
272 						goto unknown_num;
273 					if ((j = Numb[i].val) < 0) {
274 					    Error(WARN, LINE, " \\h < 0 ",
275 					    NULL);
276 					    break;
277 					}
278 					if (j == 0)
279 						break;
280 					if ((strlen((char *)s1+1) + j + 1)
281 					>=  MAXLINE)
282 						goto line_too_long;
283 					for (s2 = &xbuf[1]; j; j--)
284 						*s2++ = ' ';
285 					(void) strcpy((char *)s2, (char *)s1+1);
286 					s1 = xbuf;
287 					break;
288 			    /*
289 			     * Save current position in register if "\\k<reg>"
290 			     */
291 			        case 'k':
292 					s1 = Asmcode(&s1, nm);
293 					if ((i = Findnum(nm, 0, 0)) < 0)
294 					    i = Findnum(nm, 0, 1);
295 					Numb[i].val =
296 						(int)((double)Outll * Scalen);
297 					break;
298 			    /*
299 			     * Interpolate number - "\\n(NN" or "\\nN"
300 			     */
301 				case 'n':
302 					s1 = Asmcode(&s1, nm);
303 					if ((i = Findnum(nm, 0, 0)) < 0) {
304 unknown_num:
305 					    Error(WARN, LINE,
306 					        " unknown number register ",
307 						(char *)nm);
308 					    break;
309 					}
310 					(void) sprintf((char *)buf, "%d",
311 					    Numb[i].val);
312 					if ((strlen((char *)buf)
313 					   + strlen((char *)s1+1) + 1)
314 					>=  MAXLINE) {
315 line_too_long:
316 					    Error(WARN, LINE, " line too long",
317 					        NULL);
318 					    break;
319 					}
320 					(void) sprintf((char *)buf, "%d%s",
321 					    Numb[i].val, (char *)s1+1);
322 					(void) strcpy((char *)&xbuf[1],
323 						(char *)buf);
324 				        s1 = xbuf;
325 					break;
326 			    /*
327 			     * Change size - "\\s[+-][0-9]" - NOP
328 			     */
329 				case 's':
330 					s1++;
331 					if (*s1 == '+' || *s1 == '-')
332 						s1++;
333 					while (*s1 && isdigit(*s1))
334 						s1++;
335 					s1--;
336 					break;
337 			    /*
338 			     * Continue - "\\c"
339 			     */
340 				case 'c':
341 					Backc = 1;
342 					break;
343 			    /*
344 			     * Interpolate string - "\\*(NN" or "\\*N"
345 			     */
346 				case '*':
347 					s1 = Asmcode(&s1, nm);
348 					s2 = Findstr(nm, NULL, 0);
349 					if (*s2 != '\0') {
350 					    if ((strlen((char *)s2)
351 					       + strlen((char *)s1+1) + 1)
352 					    >=  MAXLINE)
353 						goto line_too_long;
354 					    (void) sprintf((char *)buf, "%s%s",
355 						(char *)s2, (char *)s1+1);
356 					    (void) strcpy((char *)&xbuf[1],
357 						(char *)buf);
358 					    s1 = xbuf;
359 					}
360 					break;
361 			    /*
362 			     * Discretionary hyphen - "\\%"
363 			     */
364 				case '%':
365 					if (Wordl <= 0)
366 					    break;
367 					if ((i = Findhy(NULL, 0, 0)) < 0) {
368 					    Error(WARN, LINE,
369 					        " no hyphen for font ",
370 						(char *)Font);
371 					    break;
372 					}
373 					Endword();
374 					Pass3(Wordl, Word, NULL, Hychar[i].len);
375 					Pass3(NOBREAK,
376 					    (unsigned char *) "hyphen",
377 					    Hychar[i].str, Hychar[i].len);
378 					Wordl = Wordx = 0;
379 					Uhyph = 1;
380 					break;
381 			    /*
382 			     * None of the above - may be special character
383 			     * name.
384 			     */
385 				default:
386 					s2 = s1--;
387 					s1 = Asmcode(&s1, nm);
388 					if ((i = Findchar(nm, 0, NULL, 0)) < 0){
389 					    s1 = s2;
390 					    goto ordinary_char;
391 					}
392 					if (strcmp((char *)nm, "em") == 0
393 					&& Wordx > 0) {
394 				    /*
395 				     * "\\(em" is a special case when a word
396 				     * has been assembled, because of
397 				     * hyphenation.
398 				     */
399 					    Endword();
400 					    Pass3(Wordl, Word, NULL,
401 					        Schar[i].len);
402 					    Pass3(NOBREAK,
403 						(unsigned char *)"userhyphen",
404 					        Schar[i].str, Schar[i].len);
405 				            Wordl = Wordx = 0;
406 					    period = '\0';
407 					    Uhyph = 1;
408 			 		}
409 				    /*
410 				     * Interpolate a special character
411 				     */
412 					if (Str2word(Schar[i].str,
413 					    strlen((char *)Schar[i].str)) != 0)
414 						return;
415 				        Wordl += Schar[i].len;
416 					period = '\0';
417 				}
418 				break;
419 		    /*
420 		     * Ordinary character
421 		     */
422 			default:
423 ordinary_char:
424 				if (Str2word(s1, 1) != 0)
425 					return;
426 				Wordl++;
427 				if (*s1 == '.' || *s1 == '!'
428 				||  *s1 == '?' || *s1 == ':')
429 				    period = '.';
430 				else if (period == '.') {
431 				    nm[0] = *s1;
432 				    nm[1] = '\0';
433 				    if (regexec(Pat[13].pat, nm) == 0)
434 					 period = '\0';
435 				}
436 			}
437 		}
438 	    /*
439 	     * End of line processing
440 	     */
441      		if (!Backc) {
442 			if (period == '.')
443 				Pass3(NOBREAK, (unsigned char *)"gap", NULL, 2);
444 			if (Centering > 0) {
445 				Pass3(DOBREAK, (unsigned char *)"center", NULL,
446 				0);
447 				Centering--;
448 			} else if (!Fill)
449 				Pass3(DOBREAK, (unsigned char *)"flush", NULL,
450 				0);
451 		}
452 		if (Aftnxt == NULL)
453 			return;
454 		/* else fall through to process an "after next trap */
455 	}
456     /*
457      * Special -man macro handling.
458      */
459 	if (Marg == MANMACROS) {
460 	    /*
461 	     * A text line - "^[^.]" - is only processed when there is an
462 	     * "after next" directive.
463 	     */
464 		if (*line != '.' && *line != '\'') {
465 			if (Aftnxt != NULL) {
466 				if (regexec(Pat[9].pat, Aftnxt))  /* ",fP" */
467 					Font[0] = Prevfont;
468 				if (regexec(Pat[16].pat, Aftnxt))  /* ",fR" */
469 					Font[0] = 'R';
470 				if (regexec(Pat[10].pat, Aftnxt))  /* ",tP" */
471 					Pass3(DOBREAK,
472 						(unsigned char *)"toindent",
473 						NULL, 0);
474 				Free(&Aftnxt);
475 			}
476 			return;
477 		}
478 	    /*
479 	     * Special footer handling - "^.lF"
480 	     */
481 		if (line[1] == 'l' && line[2] == 'F') {
482 			s1 = Findstr((unsigned char *)"by", NULL, 0);
483 			s2 = Findstr((unsigned char *)"nb", NULL, 0);
484 			if (*s1 == '\0' || *s2 == '\0')
485 				(void) sprintf((char *)buf, "%s%s",
486 					(char *)s1, (char *)s2);
487 			else
488 				(void) sprintf((char *)buf, "%s; %s",
489 					(char *)s1, (char *)s2);
490 			Pass3(NOBREAK, (unsigned char *)"LF", buf, 0);
491 			return;
492 		}
493 	}
494     /*
495      * Special -ms macro handling.
496      */
497 	if (Marg == MSMACROS) {
498 	    /*
499 	     * A text line - "^[^.]" - is only processed when there is an
500 	     * "after next" directive.
501 	     */
502 		if (*line != '.' && *line != '\'') {
503 			if (Aftnxt != NULL) {
504 				if (regexec(Pat[10].pat, Aftnxt))  /* ",tP" */
505 					Pass3(DOBREAK,
506 						(unsigned char *)"toindent",
507 						NULL, 0);
508 				Free(&Aftnxt);
509 			}
510 			return;
511 		}
512 	    /*
513 	     * Numbered headings - "^[.']nH"
514 	     */
515 		if (line[1] == 'n' && line[2] == 'H') {
516 			s1 = Field(2, line, 0);
517 			if (s1 != NULL) {
518 				i = atoi((char *)s1) - 1;
519 				if (i < 0) {
520 					for (j = 0; j < MAXNHNR; j++) {
521 						Nhnr[j] = 0;
522 					}
523 					i = 0;
524 				} else if (i >= MAXNHNR) {
525 				    (void) sprintf((char *)buf,
526 					" over NH limit (%d)", MAXNHNR);
527 				    Error(WARN, LINE, (char *)buf, NULL);
528 				}
529 			} else
530 				i = 0;
531 			Nhnr[i]++;
532 			for (j = i + 1; j < MAXNHNR; j++) {
533 				Nhnr[j] = 0;
534 			}
535 			s1 = buf;
536 			for (j = 0; j <= i; j++) {
537 				(void) sprintf((char *)s1, "%d.", Nhnr[j]);
538 				s1 = buf + strlen((char *)buf);
539 			}
540 			(void) Findstr((unsigned char *)"Nh", buf, 1);
541 			return;
542 		}
543 	}
544     /*
545      * Remaining lines should begin with a '.' or '\'' unless an "after next"
546      * trap has failed.
547      */
548 	if (*line != '.' && *line != '\'') {
549 		if (Aftnxt != NULL)
550 			Error(WARN, LINE, " failed .it: ", (char *)Aftnxt);
551 		else
552 			Error(WARN, LINE, " unrecognized line ", NULL);
553 		return;
554 	}
555 	brk = (*line == '.') ? DOBREAK : NOBREAK;
556     /*
557      * Evaluate expressions for "^[.'](ta|ll|ls|in|ti|po|ne|sp|pl|nr)"
558      * Then process the requests.
559      */
560 	if (regexec(Pat[11].pat, &line[1])) {
561 	    /*
562 	     * Establish default scale factor.
563 	     */
564 		if ((line[1] == 'n' && line[2] == 'e')
565 		||  (line[1] == 's' && line[2] == 'p')
566 		||  (line[1] == 'p' && line[2] == 'l'))
567 			exscale = Scalev;
568 		else if (line[1] == 'n' && line[2] == 'r')
569 			exscale = Scaleu;
570 		else
571 			exscale = Scalen;
572 	    /*
573 	     * Determine starting argument.
574 	     */
575 		if (line[1] == 'n' && line[2] == 'r')
576 			s1 = Field(2, &line[3], 0);
577 		else
578 			s1 = Field(1, &line[3], 0);
579 	    /*
580 	     * Evaluate expressions.
581 	     */
582 		for (nexpr = 0; s1 != NULL &&*s1 != '\0'; ) {
583 			while (*s1 == ' ' || *s1 == '\t')
584 				s1++;
585 			if (*s1 == '+' || *s1 == '-')
586 				ssign = *s1++;
587 			else
588 				ssign = '\0';
589 		    /*
590 		     * Process terms.
591 		     */
592 			val = 0.0;
593 			sp = -1;
594 			c = '+';
595 			s1--;
596 			while (c == '+' || c == '*' || c == '%'
597 			||  c == ')' || c == '-' || c == '/') {
598 			    op = c;
599 			    s1++;
600 			    tscale = exscale;
601 			    tval = 0.0;
602 			/*
603 			 * Pop stack on right parenthesis.
604 			 */
605 			    if (op == ')') {
606 				tval = val;
607 				if (sp >= 0) {
608 				    val = valstack[sp];
609 				    op = opstack[sp];
610 				    sp--;
611 				} else {
612 				    Error(WARN, LINE,
613 					" expression stack underflow", NULL);
614 				    return;
615 				}
616 				tscale = Scaleu;
617 			/*
618 			 * Push stack on left parenthesis.
619 			 */
620 			    } else if (*s1 == '(') {
621 				sp++;
622 				if (sp >= MAXSP) {
623 				    Error(WARN, LINE,
624 				       " expression stack overflow", NULL);
625 				    return;
626 				}
627 				valstack[sp] = val;
628 				opstack[sp] = op;
629 				val = 0.0;
630 				c = '+';
631 				continue;
632 			    } else if (*s1 == '\\') {
633 			      s1++;
634 			      switch(*s1) {
635 			/*
636 			 * "\\"" begins a comment.
637 			 */
638 			      case '"':
639 				while (*s1)
640 					s1++;
641 				break;
642 			/*
643 			 * Crude width calculation for "\\w"
644 			 */
645 			      case 'w':
646 				s2 = ++s1;
647 				if (*s1) {
648 				    s1++;
649 				    while (*s1 && *s1 != *s2)
650 					s1++;
651 				    tval = (double) (s1 - s2 - 1) * Scalen;
652 				    if (*s1)
653 					s1++;
654 				}
655 				break;
656 			/*
657 			 * Interpolate number register if "\\n".
658 			 */
659 			      case 'n':
660 				s1 = Asmcode(&s1, nm);
661 				if ((i = Findnum(nm, 0, 0)) >= 0)
662 				    tval = Numb[i].val;
663 			        s1++;
664 			     }
665 			/*
666 			 * Assemble numeric value.
667 			 */
668 			    } else if (*s1 == '.' || isdigit(*s1)) {
669 				for (i = 0; isdigit(*s1) || *s1 == '.'; s1++) {
670 				    if (*s1 == '.') {
671 					i = 10;
672 					continue;
673 				    }
674 				    d = (double) (*s1 - '0');
675 				    if (i) {
676 					tval = tval + (d / (double) i);
677 					i = i * 10;
678 				    } else
679 					tval = (tval * 10.0) + d;
680 				}
681 			    } else {
682 			/*
683 			 * It's not an expression.  Ignore extra scale.
684 			 */
685 				if ((i = Findscale((int)*s1, 0.0, 0)) < 0) {
686 				    (void) sprintf((char *)buf,
687 					" \"%s\" isn't an expression",
688 					(char *)s1);
689 				    Error(WARN, LINE, (char *)buf, NULL);
690 				}
691 				s1++;
692 			    }
693 			/*
694 			 * Add term to expression value.
695 			 */
696 			    if ((i = Findscale((int)*s1, 0.0, 0)) >= 0) {
697 				tval *= Scale[i].val;
698 				s1++;
699 			    } else
700 				tval *= tscale;
701 			    switch (op) {
702 			    case '+':
703 				val += tval;
704 				break;
705 			    case '-':
706 				val -= tval;
707 				break;
708 			    case '*':
709 				val *= tval;
710 				break;
711 			    case '/':
712 			    case '%':
713 				i = (int) val;
714 				j = (int) tval;
715 				if (j == 0) {
716 				    Error(WARN, LINE,
717 					(*s1 == '/') ? "div" : "mod",
718 				        " by 0");
719 				    return;
720 				}
721 				if (op == '/')
722 					val = (double) (i / j);
723 				else
724 					val = (double) (i % j);
725 				break;
726 			    }
727 			    c = *s1;
728 			}
729 		    /*
730 		     * Save expression value and sign.
731 		     */
732 			if (nexpr >= MAXEXP) {
733 				(void) sprintf((char *)buf,
734 				    " at expression limit of %d", MAXEXP);
735 				Error(WARN, LINE, (char *)buf, NULL);
736 				return;
737 			}
738 			exsign[nexpr] = ssign;
739 			expr[nexpr] = val;
740 			if (ssign == '-')
741 				sexpr[nexpr] = -1.0 * val;
742 			else
743 				sexpr[nexpr] = val;
744 			nexpr++;
745 			while (*s1 == ' ' || *s1 == '\t')
746 				s1++;
747 		}
748 	    /*
749 	     * Set parameters "(ll|ls|in|ti|po|pl)"
750 	     */
751 		if (regexec(Pat[12].pat, &line[1])) {
752 			nm[0] = line[1];
753 			nm[1] = line[2];
754 			if ((i = Findparms(nm)) < 0) {
755 				Error(WARN, LINE,
756 				    " can't find parameter register ",
757 				    (char *)nm);
758 				return;
759 			}
760 			if (nexpr == 0 || exscale == 0.0)
761 				j = Parms[i].prev;
762 			else if (exsign[0] == '\0'
763 			     ||  (nm[0] == 't' && nm[1] == 'i'))
764 				 j = (int)(sexpr[0] / exscale);
765 			else
766 				j = Parms[i].val + (int)(sexpr[0] / exscale);
767 			Parms[i].prev = Parms[i].val;
768 			Parms[i].val = j;
769 			nm[0] = (nexpr) ? exsign[0] : '\0';     /* for .ti */
770 			nm[1] = '\0';
771 			Pass3(brk, (unsigned char *)Parms[i].cmd, nm, j);
772 			return;
773 		}
774 		if (line[1] == 'n') {
775 			switch(line[2]) {
776 	    /*
777 	     * Need - "^[.']ne <expression>"
778 	     */
779 			case 'e':
780 				if (nexpr && Scalev > 0.0)
781 					i = (int) ((expr[0]/Scalev) + 0.99);
782 				else
783 					i = 0;
784 				Pass3(DOBREAK, (unsigned char *)"need", NULL,
785 					i);
786 				return;
787 	    /*
788 	     * Number - "^[.']nr <name> <expression>"
789 	     */
790 			case 'r':
791 				if ((s1 = Field(2, line, 0)) == NULL) {
792 				    Error(WARN, LINE, " bad number register",
793 				        NULL);
794 				    return;
795 				}
796 				if ((i = Findnum(s1, 0, 0)) < 0)
797 				    i = Findnum(s1, 0, 1);
798 				if (nexpr < 1) {
799 				    Numb[i].val = 0;
800 				    return;
801 				}
802 				if (exsign[0] == '\0')
803 				    Numb[i].val = (int) expr[0];
804 				else
805 				    Numb[i].val += (int) sexpr[0];
806 				return;
807 			}
808 		}
809 	    /*
810 	     * Space - "^[.']sp <expression>"
811 	     */
812 		if (line[1] == 's' && line[2] == 'p') {
813 			if (nexpr == 0)
814 				i = 1;
815 			else
816 				i = (int)((expr[0] / Scalev) + 0.99);
817 			while (i--)
818 				Pass3(brk, (unsigned char *)"space", NULL, 0);
819 			return;
820 		}
821 	    /*
822 	     * Tab positions - "^[.']ta <pos1> <pos2> . . ."
823 	     */
824      		if (line[1] == 't' && line[2] == 'a') {
825 			tval = 0.0;
826 			for (j = 0; j < nexpr; j++) {
827 				if (exsign[j] == '\0')
828 					tval = expr[j];
829 				else
830 					tval += sexpr[j];
831 				Tabs[j] = (int) (tval / Scalen);
832 			}
833 			Ntabs = nexpr;
834 			return;
835 		}
836 	}
837     /*
838      * Process all other nroff requests via Nreq().
839      */
840 	(void) Nreq(line, brk);
841 	return;
842 }
843