1 /****************************************************************************
2  * Copyright 2018-2019,2020 Thomas E. Dickey                                *
3  * Copyright 1998-2016,2017 Free Software Foundation, Inc.                  *
4  *                                                                          *
5  * Permission is hereby granted, free of charge, to any person obtaining a  *
6  * copy of this software and associated documentation files (the            *
7  * "Software"), to deal in the Software without restriction, including      *
8  * without limitation the rights to use, copy, modify, merge, publish,      *
9  * distribute, distribute with modifications, sublicense, and/or sell       *
10  * copies of the Software, and to permit persons to whom the Software is    *
11  * furnished to do so, subject to the following conditions:                 *
12  *                                                                          *
13  * The above copyright notice and this permission notice shall be included  *
14  * in all copies or substantial portions of the Software.                   *
15  *                                                                          *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS  *
17  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF               *
18  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.   *
19  * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,   *
20  * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR    *
21  * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR    *
22  * THE USE OR OTHER DEALINGS IN THE SOFTWARE.                               *
23  *                                                                          *
24  * Except as contained in this notice, the name(s) of the above copyright   *
25  * holders shall not be used in advertising or otherwise to promote the     *
26  * sale, use or other dealings in this Software without prior written       *
27  * authorization.                                                           *
28  ****************************************************************************/
29 
30 /****************************************************************************
31  *  Author: Zeyd M. Ben-Halim <zmbenhal@netcom.com> 1992,1995               *
32  *     and: Eric S. Raymond <esr@snark.thyrsus.com>                         *
33  *     and: Thomas E. Dickey, 1996 on                                       *
34  ****************************************************************************/
35 
36 /*
37  *	tparm.c
38  *
39  */
40 
41 #include <curses.priv.h>
42 
43 #include <ctype.h>
44 #include <tic.h>
45 
46 MODULE_ID("$Id: lib_tparm.c,v 1.108 2020/02/02 23:34:34 tom Exp $")
47 
48 /*
49  *	char *
50  *	tparm(string, ...)
51  *
52  *	Substitute the given parameters into the given string by the following
53  *	rules (taken from terminfo(5)):
54  *
55  *	     Cursor addressing and other strings  requiring  parame-
56  *	ters in the terminal are described by a parameterized string
57  *	capability, with escapes like %x in  it.   For  example,  to
58  *	address  the  cursor, the cup capability is given, using two
59  *	parameters: the row and column to  address  to.   (Rows  and
60  *	columns  are  numbered  from  zero and refer to the physical
61  *	screen visible to the user, not to any  unseen  memory.)  If
62  *	the terminal has memory relative cursor addressing, that can
63  *	be indicated by
64  *
65  *	     The parameter mechanism uses  a  stack  and  special  %
66  *	codes  to manipulate it.  Typically a sequence will push one
67  *	of the parameters onto the stack and then print it  in  some
68  *	format.  Often more complex operations are necessary.
69  *
70  *	     The % encodings have the following meanings:
71  *
72  *	     %%        outputs `%'
73  *	     %c        print pop() like %c in printf()
74  *	     %s        print pop() like %s in printf()
75  *           %[[:]flags][width[.precision]][doxXs]
76  *                     as in printf, flags are [-+#] and space
77  *                     The ':' is used to avoid making %+ or %-
78  *                     patterns (see below).
79  *
80  *	     %p[1-9]   push ith parm
81  *	     %P[a-z]   set dynamic variable [a-z] to pop()
82  *	     %g[a-z]   get dynamic variable [a-z] and push it
83  *	     %P[A-Z]   set static variable [A-Z] to pop()
84  *	     %g[A-Z]   get static variable [A-Z] and push it
85  *	     %l        push strlen(pop)
86  *	     %'c'      push char constant c
87  *	     %{nn}     push integer constant nn
88  *
89  *	     %+ %- %* %/ %m
90  *	               arithmetic (%m is mod): push(pop() op pop())
91  *	     %& %| %^  bit operations: push(pop() op pop())
92  *	     %= %> %<  logical operations: push(pop() op pop())
93  *	     %A %O     logical and & or operations for conditionals
94  *	     %! %~     unary operations push(op pop())
95  *	     %i        add 1 to first two parms (for ANSI terminals)
96  *
97  *	     %? expr %t thenpart %e elsepart %;
98  *	               if-then-else, %e elsepart is optional.
99  *	               else-if's are possible ala Algol 68:
100  *	               %? c1 %t b1 %e c2 %t b2 %e c3 %t b3 %e c4 %t b4 %e b5 %;
101  *
102  *	For those of the above operators which are binary and not commutative,
103  *	the stack works in the usual way, with
104  *			%gx %gy %m
105  *	resulting in x mod y, not the reverse.
106  */
107 
108 NCURSES_EXPORT_VAR(int) _nc_tparm_err = 0;
109 
110 #define TPS(var) _nc_prescreen.tparm_state.var
111 #define popcount _nc_popcount	/* workaround for NetBSD 6.0 defect */
112 
113 #if NO_LEAKS
114 NCURSES_EXPORT(void)
115 _nc_free_tparm(void)
116 {
117     if (TPS(out_buff) != 0) {
118 	FreeAndNull(TPS(out_buff));
119 	TPS(out_size) = 0;
120 	TPS(out_used) = 0;
121 	FreeAndNull(TPS(fmt_buff));
122 	TPS(fmt_size) = 0;
123     }
124 }
125 #endif
126 
127 static NCURSES_INLINE void
128 get_space(size_t need)
129 {
130     need += TPS(out_used);
131     if (need > TPS(out_size)) {
132 	TPS(out_size) = need * 2;
133 	TYPE_REALLOC(char, TPS(out_size), TPS(out_buff));
134     }
135 }
136 
137 static NCURSES_INLINE void
138 save_text(const char *fmt, const char *s, int len)
139 {
140     size_t s_len = strlen(s);
141     if (len > (int) s_len)
142 	s_len = (size_t) len;
143 
144     get_space(s_len + 1);
145 
146     _nc_SPRINTF(TPS(out_buff) + TPS(out_used),
147 		_nc_SLIMIT(TPS(out_size) - TPS(out_used))
148 		fmt, s);
149     TPS(out_used) += strlen(TPS(out_buff) + TPS(out_used));
150 }
151 
152 static NCURSES_INLINE void
153 save_number(const char *fmt, int number, int len)
154 {
155     if (len < 30)
156 	len = 30;		/* actually log10(MAX_INT)+1 */
157 
158     get_space((size_t) len + 1);
159 
160     _nc_SPRINTF(TPS(out_buff) + TPS(out_used),
161 		_nc_SLIMIT(TPS(out_size) - TPS(out_used))
162 		fmt, number);
163     TPS(out_used) += strlen(TPS(out_buff) + TPS(out_used));
164 }
165 
166 static NCURSES_INLINE void
167 save_char(int c)
168 {
169     if (c == 0)
170 	c = 0200;
171     get_space((size_t) 1);
172     TPS(out_buff)[TPS(out_used)++] = (char) c;
173 }
174 
175 static NCURSES_INLINE void
176 npush(int x)
177 {
178     if (TPS(stack_ptr) < STACKSIZE) {
179 	TPS(stack)[TPS(stack_ptr)].num_type = TRUE;
180 	TPS(stack)[TPS(stack_ptr)].data.num = x;
181 	TPS(stack_ptr)++;
182     } else {
183 	DEBUG(2, ("npush: stack overflow: %s", _nc_visbuf(TPS(tparam_base))));
184 	_nc_tparm_err++;
185     }
186 }
187 
188 static NCURSES_INLINE int
189 npop(void)
190 {
191     int result = 0;
192     if (TPS(stack_ptr) > 0) {
193 	TPS(stack_ptr)--;
194 	if (TPS(stack)[TPS(stack_ptr)].num_type)
195 	    result = TPS(stack)[TPS(stack_ptr)].data.num;
196     } else {
197 	DEBUG(2, ("npop: stack underflow: %s", _nc_visbuf(TPS(tparam_base))));
198 	_nc_tparm_err++;
199     }
200     return result;
201 }
202 
203 static NCURSES_INLINE void
204 spush(char *x)
205 {
206     if (TPS(stack_ptr) < STACKSIZE) {
207 	TPS(stack)[TPS(stack_ptr)].num_type = FALSE;
208 	TPS(stack)[TPS(stack_ptr)].data.str = x;
209 	TPS(stack_ptr)++;
210     } else {
211 	DEBUG(2, ("spush: stack overflow: %s", _nc_visbuf(TPS(tparam_base))));
212 	_nc_tparm_err++;
213     }
214 }
215 
216 static NCURSES_INLINE char *
217 spop(void)
218 {
219     static char dummy[] = "";	/* avoid const-cast */
220     char *result = dummy;
221     if (TPS(stack_ptr) > 0) {
222 	TPS(stack_ptr)--;
223 	if (!TPS(stack)[TPS(stack_ptr)].num_type
224 	    && TPS(stack)[TPS(stack_ptr)].data.str != 0)
225 	    result = TPS(stack)[TPS(stack_ptr)].data.str;
226     } else {
227 	DEBUG(2, ("spop: stack underflow: %s", _nc_visbuf(TPS(tparam_base))));
228 	_nc_tparm_err++;
229     }
230     return result;
231 }
232 
233 static NCURSES_INLINE const char *
234 parse_format(const char *s, char *format, int *len)
235 {
236     *len = 0;
237     if (format != 0) {
238 	bool done = FALSE;
239 	bool allowminus = FALSE;
240 	bool dot = FALSE;
241 	bool err = FALSE;
242 	char *fmt = format;
243 	int my_width = 0;
244 	int my_prec = 0;
245 	int value = 0;
246 
247 	*len = 0;
248 	*format++ = '%';
249 	while (*s != '\0' && !done) {
250 	    switch (*s) {
251 	    case 'c':		/* FALLTHRU */
252 	    case 'd':		/* FALLTHRU */
253 	    case 'o':		/* FALLTHRU */
254 	    case 'x':		/* FALLTHRU */
255 	    case 'X':		/* FALLTHRU */
256 	    case 's':
257 #ifdef EXP_XTERM_1005
258 	    case 'u':
259 #endif
260 		*format++ = *s;
261 		done = TRUE;
262 		break;
263 	    case '.':
264 		*format++ = *s++;
265 		if (dot) {
266 		    err = TRUE;
267 		} else {	/* value before '.' is the width */
268 		    dot = TRUE;
269 		    my_width = value;
270 		}
271 		value = 0;
272 		break;
273 	    case '#':
274 		*format++ = *s++;
275 		break;
276 	    case ' ':
277 		*format++ = *s++;
278 		break;
279 	    case ':':
280 		s++;
281 		allowminus = TRUE;
282 		break;
283 	    case '-':
284 		if (allowminus) {
285 		    *format++ = *s++;
286 		} else {
287 		    done = TRUE;
288 		}
289 		break;
290 	    default:
291 		if (isdigit(UChar(*s))) {
292 		    value = (value * 10) + (*s - '0');
293 		    if (value > 10000)
294 			err = TRUE;
295 		    *format++ = *s++;
296 		} else {
297 		    done = TRUE;
298 		}
299 	    }
300 	}
301 
302 	/*
303 	 * If we found an error, ignore (and remove) the flags.
304 	 */
305 	if (err) {
306 	    my_width = my_prec = value = 0;
307 	    format = fmt;
308 	    *format++ = '%';
309 	    *format++ = *s;
310 	}
311 
312 	/*
313 	 * Any value after '.' is the precision.  If we did not see '.', then
314 	 * the value is the width.
315 	 */
316 	if (dot)
317 	    my_prec = value;
318 	else
319 	    my_width = value;
320 
321 	*format = '\0';
322 	/* return maximum string length in print */
323 	*len = (my_width > my_prec) ? my_width : my_prec;
324     }
325     return s;
326 }
327 
328 #define isUPPER(c) ((c) >= 'A' && (c) <= 'Z')
329 #define isLOWER(c) ((c) >= 'a' && (c) <= 'z')
330 #define tc_BUMP()  if (level < 0 && number < 2) number++
331 
332 /*
333  * Analyze the string to see how many parameters we need from the varargs list,
334  * and what their types are.  We will only accept string parameters if they
335  * appear as a %l or %s format following an explicit parameter reference (e.g.,
336  * %p2%s).  All other parameters are numbers.
337  *
338  * 'number' counts coarsely the number of pop's we see in the string, and
339  * 'popcount' shows the highest parameter number in the string.  We would like
340  * to simply use the latter count, but if we are reading termcap strings, there
341  * may be cases that we cannot see the explicit parameter numbers.
342  */
343 NCURSES_EXPORT(int)
344 _nc_tparm_analyze(const char *string, char *p_is_s[NUM_PARM], int *popcount)
345 {
346     size_t len2;
347     int i;
348     int lastpop = -1;
349     int len;
350     int number = 0;
351     int level = -1;
352     const char *cp = string;
353     static char dummy[] = "";
354 
355     if (cp == 0)
356 	return 0;
357 
358     if ((len2 = strlen(cp)) + 2 > TPS(fmt_size)) {
359 	TPS(fmt_size) += len2 + 2;
360 	TPS(fmt_buff) = typeRealloc(char, TPS(fmt_size), TPS(fmt_buff));
361 	if (TPS(fmt_buff) == 0)
362 	    return 0;
363     }
364 
365     memset(p_is_s, 0, sizeof(p_is_s[0]) * NUM_PARM);
366     *popcount = 0;
367 
368     while ((cp - string) < (int) len2) {
369 	if (*cp == '%') {
370 	    cp++;
371 	    cp = parse_format(cp, TPS(fmt_buff), &len);
372 	    switch (*cp) {
373 	    default:
374 		break;
375 
376 	    case 'd':		/* FALLTHRU */
377 	    case 'o':		/* FALLTHRU */
378 	    case 'x':		/* FALLTHRU */
379 	    case 'X':		/* FALLTHRU */
380 	    case 'c':		/* FALLTHRU */
381 #ifdef EXP_XTERM_1005
382 	    case 'u':
383 #endif
384 		if (lastpop <= 0) {
385 		    tc_BUMP();
386 		}
387 		level -= 1;
388 		lastpop = -1;
389 		break;
390 
391 	    case 'l':
392 	    case 's':
393 		if (lastpop > 0) {
394 		    level -= 1;
395 		    p_is_s[lastpop - 1] = dummy;
396 		}
397 		tc_BUMP();
398 		break;
399 
400 	    case 'p':
401 		cp++;
402 		i = (UChar(*cp) - '0');
403 		if (i >= 0 && i <= NUM_PARM) {
404 		    ++level;
405 		    lastpop = i;
406 		    if (lastpop > *popcount)
407 			*popcount = lastpop;
408 		}
409 		break;
410 
411 	    case 'P':
412 		++cp;
413 		break;
414 
415 	    case 'g':
416 		++level;
417 		cp++;
418 		break;
419 
420 	    case S_QUOTE:
421 		++level;
422 		cp += 2;
423 		lastpop = -1;
424 		break;
425 
426 	    case L_BRACE:
427 		++level;
428 		cp++;
429 		while (isdigit(UChar(*cp))) {
430 		    cp++;
431 		}
432 		break;
433 
434 	    case '+':
435 	    case '-':
436 	    case '*':
437 	    case '/':
438 	    case 'm':
439 	    case 'A':
440 	    case 'O':
441 	    case '&':
442 	    case '|':
443 	    case '^':
444 	    case '=':
445 	    case '<':
446 	    case '>':
447 		tc_BUMP();
448 		level -= 1;	/* pop 2, operate, push 1 */
449 		lastpop = -1;
450 		break;
451 
452 	    case '!':
453 	    case '~':
454 		tc_BUMP();
455 		lastpop = -1;
456 		break;
457 
458 	    case 'i':
459 		/* will add 1 to first (usually two) parameters */
460 		break;
461 	    }
462 	}
463 	if (*cp != '\0')
464 	    cp++;
465     }
466 
467     if (number > NUM_PARM)
468 	number = NUM_PARM;
469     return number;
470 }
471 
472 static NCURSES_INLINE char *
473 tparam_internal(int use_TPARM_ARG, const char *string, va_list ap)
474 {
475     char *p_is_s[NUM_PARM];
476     TPARM_ARG param[NUM_PARM];
477     int popcount = 0;
478     int number;
479     int num_args;
480     int len;
481     int level;
482     int x, y;
483     int i;
484     const char *cp = string;
485     size_t len2;
486     bool termcap_hack;
487     bool incremented_two;
488 
489     if (cp == NULL) {
490 	TR(TRACE_CALLS, ("%s: format is null", TPS(tname)));
491 	return NULL;
492     }
493 
494     TPS(out_used) = 0;
495     len2 = strlen(cp);
496 
497     /*
498      * Find the highest parameter-number referred to in the format string.
499      * Use this value to limit the number of arguments copied from the
500      * variable-length argument list.
501      */
502     number = _nc_tparm_analyze(cp, p_is_s, &popcount);
503     if (TPS(fmt_buff) == 0) {
504 	TR(TRACE_CALLS, ("%s: error in analysis", TPS(tname)));
505 	return NULL;
506     }
507 
508     incremented_two = FALSE;
509 
510     if (number > NUM_PARM)
511 	number = NUM_PARM;
512     if (popcount > NUM_PARM)
513 	popcount = NUM_PARM;
514     num_args = max(popcount, number);
515 
516     for (i = 0; i < num_args; i++) {
517 	/*
518 	 * A few caps (such as plab_norm) have string-valued parms.
519 	 * We'll have to assume that the caller knows the difference, since
520 	 * a char* and an int may not be the same size on the stack.  The
521 	 * normal prototype for this uses 9 long's, which is consistent with
522 	 * our va_arg() usage.
523 	 */
524 	if (p_is_s[i] != 0) {
525 	    p_is_s[i] = va_arg(ap, char *);
526 	    param[i] = 0;
527 	} else if (use_TPARM_ARG) {
528 	    param[i] = va_arg(ap, TPARM_ARG);
529 	} else {
530 	    param[i] = (TPARM_ARG) va_arg(ap, int);
531 	}
532     }
533 
534     /*
535      * This is a termcap compatibility hack.  If there are no explicit pop
536      * operations in the string, load the stack in such a way that
537      * successive pops will grab successive parameters.  That will make
538      * the expansion of (for example) \E[%d;%dH work correctly in termcap
539      * style, which means tparam() will expand termcap strings OK.
540      */
541     TPS(stack_ptr) = 0;
542     termcap_hack = FALSE;
543     if (popcount == 0) {
544 	termcap_hack = TRUE;
545 	for (i = number - 1; i >= 0; i--) {
546 	    if (p_is_s[i])
547 		spush(p_is_s[i]);
548 	    else
549 		npush((int) param[i]);
550 	}
551     }
552 #ifdef TRACE
553     if (USE_TRACEF(TRACE_CALLS)) {
554 	for (i = 0; i < num_args; i++) {
555 	    if (p_is_s[i] != 0) {
556 		save_text(", %s", _nc_visbuf(p_is_s[i]), 0);
557 	    } else if ((long) param[i] > MAX_OF_TYPE(NCURSES_INT2) ||
558 		       (long) param[i] < 0) {
559 		_tracef("BUG: problem with tparm parameter #%d of %d",
560 			i + 1, num_args);
561 		break;
562 	    } else {
563 		save_number(", %d", (int) param[i], 0);
564 	    }
565 	}
566 	_tracef(T_CALLED("%s(%s%s)"), TPS(tname), _nc_visbuf(cp), TPS(out_buff));
567 	TPS(out_used) = 0;
568 	_nc_unlock_global(tracef);
569     }
570 #endif /* TRACE */
571 
572     while ((cp - string) < (int) len2) {
573 	if (*cp != '%') {
574 	    save_char(UChar(*cp));
575 	} else {
576 	    TPS(tparam_base) = cp++;
577 	    cp = parse_format(cp, TPS(fmt_buff), &len);
578 	    switch (*cp) {
579 	    default:
580 		break;
581 	    case '%':
582 		save_char('%');
583 		break;
584 
585 	    case 'd':		/* FALLTHRU */
586 	    case 'o':		/* FALLTHRU */
587 	    case 'x':		/* FALLTHRU */
588 	    case 'X':		/* FALLTHRU */
589 		save_number(TPS(fmt_buff), npop(), len);
590 		break;
591 
592 	    case 'c':		/* FALLTHRU */
593 		save_char(npop());
594 		break;
595 
596 #ifdef EXP_XTERM_1005
597 	    case 'u':
598 		{
599 		    unsigned char target[10];
600 		    unsigned source = (unsigned) npop();
601 		    int rc = _nc_conv_to_utf8(target, source, (unsigned)
602 					      sizeof(target));
603 		    int n;
604 		    for (n = 0; n < rc; ++n) {
605 			save_char(target[n]);
606 		    }
607 		}
608 		break;
609 #endif
610 	    case 'l':
611 		npush((int) strlen(spop()));
612 		break;
613 
614 	    case 's':
615 		save_text(TPS(fmt_buff), spop(), len);
616 		break;
617 
618 	    case 'p':
619 		cp++;
620 		i = (UChar(*cp) - '1');
621 		if (i >= 0 && i < NUM_PARM) {
622 		    if (p_is_s[i]) {
623 			spush(p_is_s[i]);
624 		    } else {
625 			npush((int) param[i]);
626 		    }
627 		}
628 		break;
629 
630 	    case 'P':
631 		cp++;
632 		if (isUPPER(*cp)) {
633 		    i = (UChar(*cp) - 'A');
634 		    TPS(static_vars)[i] = npop();
635 		} else if (isLOWER(*cp)) {
636 		    i = (UChar(*cp) - 'a');
637 		    TPS(dynamic_var)[i] = npop();
638 		}
639 		break;
640 
641 	    case 'g':
642 		cp++;
643 		if (isUPPER(*cp)) {
644 		    i = (UChar(*cp) - 'A');
645 		    npush(TPS(static_vars)[i]);
646 		} else if (isLOWER(*cp)) {
647 		    i = (UChar(*cp) - 'a');
648 		    npush(TPS(dynamic_var)[i]);
649 		}
650 		break;
651 
652 	    case S_QUOTE:
653 		cp++;
654 		npush(UChar(*cp));
655 		cp++;
656 		break;
657 
658 	    case L_BRACE:
659 		number = 0;
660 		cp++;
661 		while (isdigit(UChar(*cp))) {
662 		    number = (number * 10) + (UChar(*cp) - '0');
663 		    cp++;
664 		}
665 		npush(number);
666 		break;
667 
668 	    case '+':
669 		npush(npop() + npop());
670 		break;
671 
672 	    case '-':
673 		y = npop();
674 		x = npop();
675 		npush(x - y);
676 		break;
677 
678 	    case '*':
679 		npush(npop() * npop());
680 		break;
681 
682 	    case '/':
683 		y = npop();
684 		x = npop();
685 		npush(y ? (x / y) : 0);
686 		break;
687 
688 	    case 'm':
689 		y = npop();
690 		x = npop();
691 		npush(y ? (x % y) : 0);
692 		break;
693 
694 	    case 'A':
695 		y = npop();
696 		x = npop();
697 		npush(y && x);
698 		break;
699 
700 	    case 'O':
701 		y = npop();
702 		x = npop();
703 		npush(y || x);
704 		break;
705 
706 	    case '&':
707 		npush(npop() & npop());
708 		break;
709 
710 	    case '|':
711 		npush(npop() | npop());
712 		break;
713 
714 	    case '^':
715 		npush(npop() ^ npop());
716 		break;
717 
718 	    case '=':
719 		y = npop();
720 		x = npop();
721 		npush(x == y);
722 		break;
723 
724 	    case '<':
725 		y = npop();
726 		x = npop();
727 		npush(x < y);
728 		break;
729 
730 	    case '>':
731 		y = npop();
732 		x = npop();
733 		npush(x > y);
734 		break;
735 
736 	    case '!':
737 		npush(!npop());
738 		break;
739 
740 	    case '~':
741 		npush(~npop());
742 		break;
743 
744 	    case 'i':
745 		/*
746 		 * Increment the first two parameters -- if they are numbers
747 		 * rather than strings.  As a side effect, assign into the
748 		 * stack; if this is termcap, then the stack was populated
749 		 * using the termcap hack above rather than via the terminfo
750 		 * 'p' case.
751 		 */
752 		if (!incremented_two) {
753 		    incremented_two = TRUE;
754 		    if (p_is_s[0] == 0) {
755 			param[0]++;
756 			if (termcap_hack)
757 			    TPS(stack)[0].data.num = (int) param[0];
758 		    }
759 		    if (p_is_s[1] == 0) {
760 			param[1]++;
761 			if (termcap_hack)
762 			    TPS(stack)[1].data.num = (int) param[1];
763 		    }
764 		}
765 		break;
766 
767 	    case '?':
768 		break;
769 
770 	    case 't':
771 		x = npop();
772 		if (!x) {
773 		    /* scan forward for %e or %; at level zero */
774 		    cp++;
775 		    level = 0;
776 		    while (*cp) {
777 			if (*cp == '%') {
778 			    cp++;
779 			    if (*cp == '?')
780 				level++;
781 			    else if (*cp == ';') {
782 				if (level > 0)
783 				    level--;
784 				else
785 				    break;
786 			    } else if (*cp == 'e' && level == 0)
787 				break;
788 			}
789 
790 			if (*cp)
791 			    cp++;
792 		    }
793 		}
794 		break;
795 
796 	    case 'e':
797 		/* scan forward for a %; at level zero */
798 		cp++;
799 		level = 0;
800 		while (*cp) {
801 		    if (*cp == '%') {
802 			cp++;
803 			if (*cp == '?')
804 			    level++;
805 			else if (*cp == ';') {
806 			    if (level > 0)
807 				level--;
808 			    else
809 				break;
810 			}
811 		    }
812 
813 		    if (*cp)
814 			cp++;
815 		}
816 		break;
817 
818 	    case ';':
819 		break;
820 
821 	    }			/* endswitch (*cp) */
822 	}			/* endelse (*cp == '%') */
823 
824 	if (*cp == '\0')
825 	    break;
826 
827 	cp++;
828     }				/* endwhile (*cp) */
829 
830     get_space((size_t) 1);
831     TPS(out_buff)[TPS(out_used)] = '\0';
832 
833     T((T_RETURN("%s"), _nc_visbuf(TPS(out_buff))));
834     return (TPS(out_buff));
835 }
836 
837 #if NCURSES_TPARM_VARARGS
838 #define tparm_varargs tparm
839 #else
840 #define tparm_proto tparm
841 #endif
842 
843 NCURSES_EXPORT(char *)
844 tparm_varargs(const char *string, ...)
845 {
846     va_list ap;
847     char *result;
848 
849     _nc_tparm_err = 0;
850     va_start(ap, string);
851 #ifdef TRACE
852     TPS(tname) = "tparm";
853 #endif /* TRACE */
854     result = tparam_internal(TRUE, string, ap);
855     va_end(ap);
856     return result;
857 }
858 
859 #if !NCURSES_TPARM_VARARGS
860 NCURSES_EXPORT(char *)
861 tparm_proto(const char *string,
862 	    TPARM_ARG a1,
863 	    TPARM_ARG a2,
864 	    TPARM_ARG a3,
865 	    TPARM_ARG a4,
866 	    TPARM_ARG a5,
867 	    TPARM_ARG a6,
868 	    TPARM_ARG a7,
869 	    TPARM_ARG a8,
870 	    TPARM_ARG a9)
871 {
872     return tparm_varargs(string, a1, a2, a3, a4, a5, a6, a7, a8, a9);
873 }
874 #endif /* NCURSES_TPARM_VARARGS */
875 
876 NCURSES_EXPORT(char *)
877 tiparm(const char *string, ...)
878 {
879     va_list ap;
880     char *result;
881 
882     _nc_tparm_err = 0;
883     va_start(ap, string);
884 #ifdef TRACE
885     TPS(tname) = "tiparm";
886 #endif /* TRACE */
887     result = tparam_internal(FALSE, string, ap);
888     va_end(ap);
889     return result;
890 }
891