xref: /openbsd/lib/libcurses/tinfo/lib_tparm.c (revision c7ef0cfc)
1 /* $OpenBSD: lib_tparm.c,v 1.11 2023/10/17 09:52:09 nicm Exp $ */
2 
3 /****************************************************************************
4  * Copyright 2018-2021,2023 Thomas E. Dickey                                *
5  * Copyright 1998-2016,2017 Free Software Foundation, Inc.                  *
6  *                                                                          *
7  * Permission is hereby granted, free of charge, to any person obtaining a  *
8  * copy of this software and associated documentation files (the            *
9  * "Software"), to deal in the Software without restriction, including      *
10  * without limitation the rights to use, copy, modify, merge, publish,      *
11  * distribute, distribute with modifications, sublicense, and/or sell       *
12  * copies of the Software, and to permit persons to whom the Software is    *
13  * furnished to do so, subject to the following conditions:                 *
14  *                                                                          *
15  * The above copyright notice and this permission notice shall be included  *
16  * in all copies or substantial portions of the Software.                   *
17  *                                                                          *
18  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS  *
19  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF               *
20  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.   *
21  * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,   *
22  * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR    *
23  * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR    *
24  * THE USE OR OTHER DEALINGS IN THE SOFTWARE.                               *
25  *                                                                          *
26  * Except as contained in this notice, the name(s) of the above copyright   *
27  * holders shall not be used in advertising or otherwise to promote the     *
28  * sale, use or other dealings in this Software without prior written       *
29  * authorization.                                                           *
30  ****************************************************************************/
31 
32 /****************************************************************************
33  *  Author: Zeyd M. Ben-Halim <zmbenhal@netcom.com> 1992,1995               *
34  *     and: Eric S. Raymond <esr@snark.thyrsus.com>                         *
35  *     and: Thomas E. Dickey, 1996 on                                       *
36  ****************************************************************************/
37 
38 /*
39  *	tparm.c
40  *
41  */
42 
43 #define entry _ncu_entry
44 #define ENTRY _ncu_ENTRY
45 
46 #include <curses.priv.h>
47 
48 #undef entry
49 #undef ENTRY
50 
51 #if HAVE_TSEARCH
52 #include <search.h>
53 #endif
54 
55 #include <ctype.h>
56 #include <tic.h>
57 
58 MODULE_ID("$Id: lib_tparm.c,v 1.11 2023/10/17 09:52:09 nicm Exp $")
59 
60 /*
61  *	char *
62  *	tparm(string, ...)
63  *
64  *	Substitute the given parameters into the given string by the following
65  *	rules (taken from terminfo(5)):
66  *
67  *	     Cursor addressing and other strings  requiring  parame-
68  *	ters in the terminal are described by a parameterized string
69  *	capability, with escapes like %x in  it.   For  example,  to
70  *	address  the  cursor, the cup capability is given, using two
71  *	parameters: the row and column to  address  to.   (Rows  and
72  *	columns  are  numbered  from  zero and refer to the physical
73  *	screen visible to the user, not to any  unseen  memory.)  If
74  *	the terminal has memory relative cursor addressing, that can
75  *	be indicated by
76  *
77  *	     The parameter mechanism uses  a  stack  and  special  %
78  *	codes  to manipulate it.  Typically a sequence will push one
79  *	of the parameters onto the stack and then print it  in  some
80  *	format.  Often more complex operations are necessary.
81  *
82  *	     The % encodings have the following meanings:
83  *
84  *	     %%        outputs `%'
85  *	     %c        print pop() like %c in printf()
86  *	     %s        print pop() like %s in printf()
87  *           %[[:]flags][width[.precision]][doxXs]
88  *                     as in printf, flags are [-+#] and space
89  *                     The ':' is used to avoid making %+ or %-
90  *                     patterns (see below).
91  *
92  *	     %p[1-9]   push ith parm
93  *	     %P[a-z]   set dynamic variable [a-z] to pop()
94  *	     %g[a-z]   get dynamic variable [a-z] and push it
95  *	     %P[A-Z]   set static variable [A-Z] to pop()
96  *	     %g[A-Z]   get static variable [A-Z] and push it
97  *	     %l        push strlen(pop)
98  *	     %'c'      push char constant c
99  *	     %{nn}     push integer constant nn
100  *
101  *	     %+ %- %* %/ %m
102  *	               arithmetic (%m is mod): push(pop() op pop())
103  *	     %& %| %^  bit operations: push(pop() op pop())
104  *	     %= %> %<  logical operations: push(pop() op pop())
105  *	     %A %O     logical and & or operations for conditionals
106  *	     %! %~     unary operations push(op pop())
107  *	     %i        add 1 to first two parms (for ANSI terminals)
108  *
109  *	     %? expr %t thenpart %e elsepart %;
110  *	               if-then-else, %e elsepart is optional.
111  *	               else-if's are possible ala Algol 68:
112  *	               %? c1 %t b1 %e c2 %t b2 %e c3 %t b3 %e c4 %t b4 %e b5 %;
113  *
114  *	For those of the above operators which are binary and not commutative,
115  *	the stack works in the usual way, with
116  *			%gx %gy %m
117  *	resulting in x mod y, not the reverse.
118  */
119 
120 NCURSES_EXPORT_VAR(int) _nc_tparm_err = 0;
121 
122 #define TPS(var) tps->var
123 #define popcount _nc_popcount	/* workaround for NetBSD 6.0 defect */
124 
125 #define get_tparm_state(term) \
126 	    (term != NULL \
127 	      ? &(term->tparm_state) \
128 	      : &(_nc_prescreen.tparm_state))
129 
130 #define isUPPER(c) ((c) >= 'A' && (c) <= 'Z')
131 #define isLOWER(c) ((c) >= 'a' && (c) <= 'z')
132 #define tc_BUMP()  if (level < 0 && number < 2) number++
133 
134 typedef struct {
135     const char *format;		/* format-string can be used as cache-key */
136     int tparm_type;		/* bit-set for each string-parameter */
137     int num_actual;
138     int num_parsed;
139     int num_popped;
140     TPARM_ARG param[NUM_PARM];
141     char *p_is_s[NUM_PARM];
142 } TPARM_DATA;
143 
144 #if HAVE_TSEARCH
145 #define MyCache _nc_globals.cached_tparm
146 #define MyCount _nc_globals.count_tparm
147 static int which_tparm;
148 static TPARM_DATA **delete_tparm;
149 #endif /* HAVE_TSEARCH */
150 
151 static char dummy[] = "";	/* avoid const-cast */
152 
153 #if HAVE_TSEARCH
154 static int
cmp_format(const void * p,const void * q)155 cmp_format(const void *p, const void *q)
156 {
157     const char *a = *(char *const *) p;
158     const char *b = *(char *const *) q;
159     return strcmp(a, b);
160 }
161 #endif
162 
163 #if HAVE_TSEARCH
164 static void
visit_nodes(const void * nodep,VISIT which,int depth)165 visit_nodes(const void *nodep, VISIT which, int depth)
166 {
167     (void) depth;
168     if (which == preorder || which == leaf) {
169 	delete_tparm[which_tparm] = *(TPARM_DATA **) nodep;
170 	which_tparm++;
171     }
172 }
173 #endif
174 
175 NCURSES_EXPORT(void)
_nc_free_tparm(TERMINAL * termp)176 _nc_free_tparm(TERMINAL *termp)
177 {
178     TPARM_STATE *tps = get_tparm_state(termp);
179 #if HAVE_TSEARCH
180     if (MyCount != 0) {
181 	delete_tparm = typeCalloc(TPARM_DATA *, MyCount);
182 	if (delete_tparm != NULL) {
183 	    which_tparm = 0;
184 	    twalk(MyCache, visit_nodes);
185 	    for (which_tparm = 0; which_tparm < MyCount; ++which_tparm) {
186 		TPARM_DATA *ptr = delete_tparm[which_tparm];
187 		if (ptr != NULL) {
188 		    tdelete(ptr, &MyCache, cmp_format);
189 		    free((char *) ptr->format);
190 		    free(ptr);
191 		}
192 	    }
193 	    which_tparm = 0;
194 	    twalk(MyCache, visit_nodes);
195 	    FreeAndNull(delete_tparm);
196 	}
197 	MyCount = 0;
198 	which_tparm = 0;
199     }
200 #endif
201     FreeAndNull(TPS(out_buff));
202     TPS(out_size) = 0;
203     TPS(out_used) = 0;
204 
205     FreeAndNull(TPS(fmt_buff));
206     TPS(fmt_size) = 0;
207 }
208 
209 static int
tparm_error(TPARM_STATE * tps,const char * message)210 tparm_error(TPARM_STATE *tps, const char *message)
211 {
212     (void) tps;
213     (void) message;
214     DEBUG(2, ("%s: %s", message, _nc_visbuf(TPS(tparam_base))));
215     return ++_nc_tparm_err;
216 }
217 
218 #define get_space(tps, need) \
219 { \
220     size_t need2get = need + TPS(out_used); \
221     if (need2get > TPS(out_size)) { \
222 	TPS(out_size) = need2get * 2; \
223 	TYPE_REALLOC(char, TPS(out_size), TPS(out_buff)); \
224     } \
225 }
226 
227 #if NCURSES_EXPANDED
228 static NCURSES_INLINE void
229   (get_space) (TPARM_STATE *tps, size_t need) {
230     get_space(tps, need);
231 }
232 
233 #undef get_space
234 #endif
235 
236 #define save_text(tps, fmt, s, len) \
237 { \
238     size_t s_len = (size_t) len + strlen(s) + strlen(fmt); \
239     get_space(tps, s_len + 1); \
240     _nc_SPRINTF(TPS(out_buff) + TPS(out_used), \
241 		_nc_SLIMIT(TPS(out_size) - TPS(out_used)) \
242 		fmt, s); \
243     TPS(out_used) += strlen(TPS(out_buff) + TPS(out_used)); \
244 }
245 
246 #if NCURSES_EXPANDED
247 static NCURSES_INLINE void
248   (save_text) (TPARM_STATE *tps, const char *fmt, const char *s, int len) {
249     save_text(tps, fmt, s, len);
250 }
251 
252 #undef save_text
253 #endif
254 
255 #define save_number(tps, fmt, number, len) \
256 { \
257     size_t s_len = (size_t) len + 30 + strlen(fmt); \
258     get_space(tps, s_len + 1); \
259     _nc_SPRINTF(TPS(out_buff) + TPS(out_used), \
260 		_nc_SLIMIT(TPS(out_size) - TPS(out_used)) \
261 		fmt, number); \
262     TPS(out_used) += strlen(TPS(out_buff) + TPS(out_used)); \
263 }
264 
265 #if NCURSES_EXPANDED
266 static NCURSES_INLINE void
267   (save_number) (TPARM_STATE *tps, const char *fmt, int number, int len) {
268     save_number(tps, fmt, number, len);
269 }
270 
271 #undef save_number
272 #endif
273 
274 #define save_char(tps, c) \
275 { \
276     get_space(tps, (size_t) 1); \
277     TPS(out_buff)[TPS(out_used)++] = (char) ((c == 0) ? 0200 : c); \
278 }
279 
280 #if NCURSES_EXPANDED
281 static NCURSES_INLINE void
282   (save_char) (TPARM_STATE *tps, int c) {
283     save_char(tps, c);
284 }
285 
286 #undef save_char
287 #endif
288 
289 #define npush(tps, x) \
290 { \
291     if (TPS(stack_ptr) < STACKSIZE) { \
292 	TPS(stack)[TPS(stack_ptr)].num_type = TRUE; \
293 	TPS(stack)[TPS(stack_ptr)].data.num = x; \
294 	TPS(stack_ptr)++; \
295     } else { \
296 	(void) tparm_error(tps, "npush: stack overflow"); \
297     } \
298 }
299 
300 #if NCURSES_EXPANDED
301 static NCURSES_INLINE void
302   (npush) (TPARM_STATE *tps, int x) {
303     npush(tps, x);
304 }
305 
306 #undef npush
307 #endif
308 
309 #define spush(tps, x) \
310 { \
311     if (TPS(stack_ptr) < STACKSIZE) { \
312 	TPS(stack)[TPS(stack_ptr)].num_type = FALSE; \
313 	TPS(stack)[TPS(stack_ptr)].data.str = x; \
314 	TPS(stack_ptr)++; \
315     } else { \
316 	(void) tparm_error(tps, "spush: stack overflow"); \
317     } \
318 }
319 
320 #if NCURSES_EXPANDED
321 static NCURSES_INLINE void
322   (spush) (TPARM_STATE *tps, char *x) {
323     spush(tps, x);
324 }
325 
326 #undef spush
327 #endif
328 
329 #define npop(tps) \
330     ((TPS(stack_ptr)-- > 0) \
331      ? ((TPS(stack)[TPS(stack_ptr)].num_type) \
332 	 ? TPS(stack)[TPS(stack_ptr)].data.num \
333 	 : 0) \
334      : (tparm_error(tps, "npop: stack underflow"), \
335         TPS(stack_ptr) = 0))
336 
337 #if NCURSES_EXPANDED
338 static NCURSES_INLINE int
339   (npop) (TPARM_STATE *tps) {
340     return npop(tps);
341 }
342 #undef npop
343 #endif
344 
345 #define spop(tps) \
346     ((TPS(stack_ptr)-- > 0) \
347      ? ((!TPS(stack)[TPS(stack_ptr)].num_type \
348         && TPS(stack)[TPS(stack_ptr)].data.str != 0) \
349          ? TPS(stack)[TPS(stack_ptr)].data.str \
350          : dummy) \
351      : (tparm_error(tps, "spop: stack underflow"), \
352         dummy))
353 
354 #if NCURSES_EXPANDED
355 static NCURSES_INLINE char *
356   (spop) (TPARM_STATE *tps) {
357     return spop(tps);
358 }
359 #undef spop
360 #endif
361 
362 static NCURSES_INLINE const char *
parse_format(const char * s,char * format,int * len)363 parse_format(const char *s, char *format, int *len)
364 {
365     *len = 0;
366     if (format != 0) {
367 	bool done = FALSE;
368 	bool allowminus = FALSE;
369 	bool dot = FALSE;
370 	bool err = FALSE;
371 	char *fmt = format;
372 	int my_width = 0;
373 	int my_prec = 0;
374 	int value = 0;
375 
376 	*len = 0;
377 	*format++ = '%';
378 	while (*s != '\0' && !done) {
379 	    switch (*s) {
380 	    case 'c':		/* FALLTHRU */
381 	    case 'd':		/* FALLTHRU */
382 	    case 'o':		/* FALLTHRU */
383 	    case 'x':		/* FALLTHRU */
384 	    case 'X':		/* FALLTHRU */
385 	    case 's':
386 #ifdef EXP_XTERM_1005
387 	    case 'u':
388 #endif
389 		*format++ = *s;
390 		done = TRUE;
391 		break;
392 	    case '.':
393 		*format++ = *s++;
394 		if (dot) {
395 		    err = TRUE;
396 		} else {	/* value before '.' is the width */
397 		    dot = TRUE;
398 		    my_width = value;
399 		}
400 		value = 0;
401 		break;
402 	    case '#':
403 		*format++ = *s++;
404 		break;
405 	    case ' ':
406 		*format++ = *s++;
407 		break;
408 	    case ':':
409 		s++;
410 		allowminus = TRUE;
411 		break;
412 	    case '-':
413 		if (allowminus) {
414 		    *format++ = *s++;
415 		} else {
416 		    done = TRUE;
417 		}
418 		break;
419 	    default:
420 		if (isdigit(UChar(*s))) {
421 		    value = (value * 10) + (*s - '0');
422 		    if (value > 10000)
423 			err = TRUE;
424 		    *format++ = *s++;
425 		} else {
426 		    done = TRUE;
427 		}
428 	    }
429 	}
430 
431 	/*
432 	 * If we found an error, ignore (and remove) the flags.
433 	 */
434 	if (err) {
435 	    my_width = my_prec = value = 0;
436 	    format = fmt;
437 	    *format++ = '%';
438 	    *format++ = *s;
439 	}
440 
441 	/*
442 	 * Any value after '.' is the precision.  If we did not see '.', then
443 	 * the value is the width.
444 	 */
445 	if (dot)
446 	    my_prec = value;
447 	else
448 	    my_width = value;
449 
450 	*format = '\0';
451 	/* return maximum string length in print */
452 	*len = (my_width > my_prec) ? my_width : my_prec;
453     }
454     return s;
455 }
456 
457 /*
458  * Analyze the string to see how many parameters we need from the varargs list,
459  * and what their types are.  We will only accept string parameters if they
460  * appear as a %l or %s format following an explicit parameter reference (e.g.,
461  * %p2%s).  All other parameters are numbers.
462  *
463  * 'number' counts coarsely the number of pop's we see in the string, and
464  * 'popcount' shows the highest parameter number in the string.  We would like
465  * to simply use the latter count, but if we are reading termcap strings, there
466  * may be cases that we cannot see the explicit parameter numbers.
467  */
468 NCURSES_EXPORT(int)
_nc_tparm_analyze(TERMINAL * term,const char * string,char ** p_is_s,int * popcount)469 _nc_tparm_analyze(TERMINAL *term, const char *string, char **p_is_s, int *popcount)
470 {
471     TPARM_STATE *tps = get_tparm_state(term);
472     size_t len2;
473     int i;
474     int lastpop = -1;
475     int len;
476     int number = 0;
477     int level = -1;
478     const char *cp = string;
479 
480     if (cp == 0)
481 	return 0;
482 
483     if ((len2 = strlen(cp)) + 2 > TPS(fmt_size)) {
484 	TPS(fmt_size) += len2 + 2;
485 	TPS(fmt_buff) = typeRealloc(char, TPS(fmt_size), TPS(fmt_buff));
486 	if (TPS(fmt_buff) == 0)
487 	    return 0;
488     }
489 
490     memset(p_is_s, 0, sizeof(p_is_s[0]) * NUM_PARM);
491     *popcount = 0;
492 
493     while ((cp - string) < (int) len2) {
494 	if (*cp == '%') {
495 	    cp++;
496 	    cp = parse_format(cp, TPS(fmt_buff), &len);
497 	    switch (*cp) {
498 	    default:
499 		break;
500 
501 	    case 'd':		/* FALLTHRU */
502 	    case 'o':		/* FALLTHRU */
503 	    case 'x':		/* FALLTHRU */
504 	    case 'X':		/* FALLTHRU */
505 	    case 'c':		/* FALLTHRU */
506 #ifdef EXP_XTERM_1005
507 	    case 'u':
508 #endif
509 		if (lastpop <= 0) {
510 		    tc_BUMP();
511 		}
512 		level -= 1;
513 		lastpop = -1;
514 		break;
515 
516 	    case 'l':
517 	    case 's':
518 		if (lastpop > 0) {
519 		    level -= 1;
520 		    p_is_s[lastpop - 1] = dummy;
521 		}
522 		tc_BUMP();
523 		break;
524 
525 	    case 'p':
526 		cp++;
527 		i = (UChar(*cp) - '0');
528 		if (i >= 0 && i <= NUM_PARM) {
529 		    ++level;
530 		    lastpop = i;
531 		    if (lastpop > *popcount)
532 			*popcount = lastpop;
533 		}
534 		break;
535 
536 	    case 'P':
537 		++cp;
538 		break;
539 
540 	    case 'g':
541 		++level;
542 		cp++;
543 		break;
544 
545 	    case S_QUOTE:
546 		++level;
547 		cp += 2;
548 		lastpop = -1;
549 		break;
550 
551 	    case L_BRACE:
552 		++level;
553 		cp++;
554 		while (isdigit(UChar(*cp))) {
555 		    cp++;
556 		}
557 		break;
558 
559 	    case '+':
560 	    case '-':
561 	    case '*':
562 	    case '/':
563 	    case 'm':
564 	    case 'A':
565 	    case 'O':
566 	    case '&':
567 	    case '|':
568 	    case '^':
569 	    case '=':
570 	    case '<':
571 	    case '>':
572 		tc_BUMP();
573 		level -= 1;	/* pop 2, operate, push 1 */
574 		lastpop = -1;
575 		break;
576 
577 	    case '!':
578 	    case '~':
579 		tc_BUMP();
580 		lastpop = -1;
581 		break;
582 
583 	    case 'i':
584 		/* will add 1 to first (usually two) parameters */
585 		break;
586 	    }
587 	}
588 	if (*cp != '\0')
589 	    cp++;
590     }
591 
592     if (number > NUM_PARM)
593 	number = NUM_PARM;
594     return number;
595 }
596 
597 /*
598  * Analyze the capability string, finding the number of parameters and their
599  * types.
600  *
601  * TODO: cache the result so that this is done once per capability per term.
602  */
603 static int
tparm_setup(TERMINAL * term,const char * string,TPARM_DATA * result)604 tparm_setup(TERMINAL *term, const char *string, TPARM_DATA *result)
605 {
606     TPARM_STATE *tps = get_tparm_state(term);
607     int rc = OK;
608 
609     TPS(out_used) = 0;
610     memset(result, 0, sizeof(*result));
611 
612     if (!VALID_STRING(string)) {
613 	TR(TRACE_CALLS, ("%s: format is invalid", TPS(tname)));
614 	rc = ERR;
615     } else {
616 #if HAVE_TSEARCH
617 	TPARM_DATA *fs;
618 	void *ft;
619 
620 	result->format = string;
621 	if ((ft = tfind(result, &MyCache, cmp_format)) != 0) {
622 	    size_t len2;
623 	    fs = *(TPARM_DATA **) ft;
624 	    *result = *fs;
625 	    if ((len2 = strlen(string)) + 2 > TPS(fmt_size)) {
626 		TPS(fmt_size) += len2 + 2;
627 		TPS(fmt_buff) = typeRealloc(char, TPS(fmt_size), TPS(fmt_buff));
628 		if (TPS(fmt_buff) == 0)
629 		    return ERR;
630 	    }
631 	} else
632 #endif
633 	{
634 	    /*
635 	     * Find the highest parameter-number referred to in the format
636 	     * string.  Use this value to limit the number of arguments copied
637 	     * from the variable-length argument list.
638 	     */
639 	    result->num_parsed = _nc_tparm_analyze(term, string,
640 						   result->p_is_s,
641 						   &(result->num_popped));
642 	    if (TPS(fmt_buff) == 0) {
643 		TR(TRACE_CALLS, ("%s: error in analysis", TPS(tname)));
644 		rc = ERR;
645 	    } else {
646 		int n;
647 
648 		if (result->num_parsed > NUM_PARM)
649 		    result->num_parsed = NUM_PARM;
650 		if (result->num_popped > NUM_PARM)
651 		    result->num_popped = NUM_PARM;
652 		result->num_actual = max(result->num_popped, result->num_parsed);
653 
654 		for (n = 0; n < result->num_actual; ++n) {
655 		    if (result->p_is_s[n])
656 			result->tparm_type |= (1 << n);
657 		}
658 #if HAVE_TSEARCH
659 		if ((fs = typeCalloc(TPARM_DATA, 1)) != 0) {
660 		    *fs = *result;
661 		    if ((fs->format = strdup(string)) != 0) {
662 			if (tsearch(fs, &MyCache, cmp_format) != 0) {
663 			    ++MyCount;
664 			} else {
665 			    free(fs);
666 			    rc = ERR;
667 			}
668 		    } else {
669 			free(fs);
670 			rc = ERR;
671 		    }
672 		} else {
673 		    rc = ERR;
674 		}
675 #endif
676 	    }
677 	}
678     }
679 
680     return rc;
681 }
682 
683 /*
684  * A few caps (such as plab_norm) have string-valued parms.  We'll have to
685  * assume that the caller knows the difference, since a char* and an int may
686  * not be the same size on the stack.  The normal prototype for tparm uses 9
687  * long's, which is consistent with our va_arg() usage.
688  */
689 static void
tparm_copy_valist(TPARM_DATA * data,int use_TPARM_ARG,va_list ap)690 tparm_copy_valist(TPARM_DATA *data, int use_TPARM_ARG, va_list ap)
691 {
692     int i;
693 
694     for (i = 0; i < data->num_actual; i++) {
695 	if (data->p_is_s[i] != 0) {
696 	    char *value = va_arg(ap, char *);
697 	    if (value == 0)
698 		value = dummy;
699 	    data->p_is_s[i] = value;
700 	    data->param[i] = 0;
701 	} else if (use_TPARM_ARG) {
702 	    data->param[i] = va_arg(ap, TPARM_ARG);
703 	} else {
704 	    data->param[i] = (TPARM_ARG) va_arg(ap, int);
705 	}
706     }
707 }
708 
709 /*
710  * This is a termcap compatibility hack.  If there are no explicit pop
711  * operations in the string, load the stack in such a way that successive pops
712  * will grab successive parameters.  That will make the expansion of (for
713  * example) \E[%d;%dH work correctly in termcap style, which means tparam()
714  * will expand termcap strings OK.
715  */
716 static bool
tparm_tc_compat(TPARM_STATE * tps,TPARM_DATA * data)717 tparm_tc_compat(TPARM_STATE *tps, TPARM_DATA *data)
718 {
719     bool termcap_hack = FALSE;
720 
721     TPS(stack_ptr) = 0;
722 
723     if (data->num_popped == 0) {
724 	int i;
725 
726 	termcap_hack = TRUE;
727 	for (i = data->num_parsed - 1; i >= 0; i--) {
728 	    if (data->p_is_s[i]) {
729 		spush(tps, data->p_is_s[i]);
730 	    } else {
731 		npush(tps, (int) data->param[i]);
732 	    }
733 	}
734     }
735     return termcap_hack;
736 }
737 
738 #ifdef TRACE
739 static void
tparm_trace_call(TPARM_STATE * tps,const char * string,TPARM_DATA * data)740 tparm_trace_call(TPARM_STATE *tps, const char *string, TPARM_DATA *data)
741 {
742     if (USE_TRACEF(TRACE_CALLS)) {
743 	int i;
744 	for (i = 0; i < data->num_actual; i++) {
745 	    if (data->p_is_s[i] != 0) {
746 		save_text(tps, ", %s", _nc_visbuf(data->p_is_s[i]), 0);
747 	    } else if ((long) data->param[i] > MAX_OF_TYPE(NCURSES_INT2) ||
748 		       (long) data->param[i] < 0) {
749 		_tracef("BUG: problem with tparm parameter #%d of %d",
750 			i + 1, data->num_actual);
751 		break;
752 	    } else {
753 		save_number(tps, ", %d", (int) data->param[i], 0);
754 	    }
755 	}
756 	_tracef(T_CALLED("%s(%s%s)"), TPS(tname), _nc_visbuf(string), TPS(out_buff));
757 	TPS(out_used) = 0;
758 	_nc_unlock_global(tracef);
759     }
760 }
761 
762 #else
763 #define tparm_trace_call(tps, string, data)	/* nothing */
764 #endif /* TRACE */
765 
766 #define init_vars(name) \
767 	if (!name##_used) { \
768 	    name##_used = TRUE; \
769 	    memset(name##_vars, 0, sizeof(name##_vars)); \
770 	}
771 
772 static NCURSES_INLINE char *
tparam_internal(TPARM_STATE * tps,const char * string,TPARM_DATA * data)773 tparam_internal(TPARM_STATE *tps, const char *string, TPARM_DATA *data)
774 {
775     int number;
776     int len;
777     int level;
778     int x, y;
779     int i;
780     const char *s;
781     const char *cp = string;
782     size_t len2 = strlen(cp);
783     bool incremented_two = FALSE;
784     bool termcap_hack = tparm_tc_compat(tps, data);
785     /*
786      * SVr4 curses stores variables 'A' to 'Z' in the TERMINAL structure (so
787      * they are initialized once to zero), and variables 'a' to 'z' on the
788      * stack in tparm, referring to the former as "static" and the latter as
789      * "dynamic".  However, it makes no check to ensure that the "dynamic"
790      * variables are initialized.
791      *
792      * Solaris xpg4 curses makes no distinction between the upper/lower, and
793      * stores the common set of 26 variables on the stack, without initializing
794      * them.
795      *
796      * In ncurses, both sets of variables are initialized on the first use.
797      */
798     bool dynamic_used = FALSE;
799     int dynamic_vars[NUM_VARS];
800 
801     tparm_trace_call(tps, string, data);
802 
803     if (TPS(fmt_buff) == NULL) {
804 	T((T_RETURN("<null>")));
805 	return NULL;
806     }
807 
808     while ((cp - string) < (int) len2) {
809 	if (*cp != '%') {
810 	    save_char(tps, UChar(*cp));
811 	} else {
812 	    TPS(tparam_base) = cp++;
813 	    cp = parse_format(cp, TPS(fmt_buff), &len);
814 	    switch (*cp) {
815 	    default:
816 		break;
817 	    case '%':
818 		save_char(tps, '%');
819 		break;
820 
821 	    case 'd':		/* FALLTHRU */
822 	    case 'o':		/* FALLTHRU */
823 	    case 'x':		/* FALLTHRU */
824 	    case 'X':		/* FALLTHRU */
825 		x = npop(tps);
826 		save_number(tps, TPS(fmt_buff), x, len);
827 		break;
828 
829 	    case 'c':		/* FALLTHRU */
830 		x = npop(tps);
831 		save_char(tps, x);
832 		break;
833 
834 #ifdef EXP_XTERM_1005
835 	    case 'u':
836 		{
837 		    unsigned char target[10];
838 		    unsigned source = (unsigned) npop(tps);
839 		    int rc = _nc_conv_to_utf8(target, source, (unsigned)
840 					      sizeof(target));
841 		    int n;
842 		    for (n = 0; n < rc; ++n) {
843 			save_char(tps, target[n]);
844 		    }
845 		}
846 		break;
847 #endif
848 	    case 'l':
849 		s = spop(tps);
850 		npush(tps, (int) strlen(s));
851 		break;
852 
853 	    case 's':
854 		s = spop(tps);
855 		save_text(tps, TPS(fmt_buff), s, len);
856 		break;
857 
858 	    case 'p':
859 		cp++;
860 		i = (UChar(*cp) - '1');
861 		if (i >= 0 && i < NUM_PARM) {
862 		    if (data->p_is_s[i]) {
863 			spush(tps, data->p_is_s[i]);
864 		    } else {
865 			npush(tps, (int) data->param[i]);
866 		    }
867 		}
868 		break;
869 
870 	    case 'P':
871 		cp++;
872 		if (isUPPER(*cp)) {
873 		    i = (UChar(*cp) - 'A');
874 		    TPS(static_vars)[i] = npop(tps);
875 		} else if (isLOWER(*cp)) {
876 		    i = (UChar(*cp) - 'a');
877 		    init_vars(dynamic);
878 		    dynamic_vars[i] = npop(tps);
879 		}
880 		break;
881 
882 	    case 'g':
883 		cp++;
884 		if (isUPPER(*cp)) {
885 		    i = (UChar(*cp) - 'A');
886 		    npush(tps, TPS(static_vars)[i]);
887 		} else if (isLOWER(*cp)) {
888 		    i = (UChar(*cp) - 'a');
889 		    init_vars(dynamic);
890 		    npush(tps, dynamic_vars[i]);
891 		}
892 		break;
893 
894 	    case S_QUOTE:
895 		cp++;
896 		npush(tps, UChar(*cp));
897 		cp++;
898 		break;
899 
900 	    case L_BRACE:
901 		number = 0;
902 		cp++;
903 		while (isdigit(UChar(*cp))) {
904 		    number = (number * 10) + (UChar(*cp) - '0');
905 		    cp++;
906 		}
907 		npush(tps, number);
908 		break;
909 
910 	    case '+':
911 		y = npop(tps);
912 		x = npop(tps);
913 		npush(tps, x + y);
914 		break;
915 
916 	    case '-':
917 		y = npop(tps);
918 		x = npop(tps);
919 		npush(tps, x - y);
920 		break;
921 
922 	    case '*':
923 		y = npop(tps);
924 		x = npop(tps);
925 		npush(tps, x * y);
926 		break;
927 
928 	    case '/':
929 		y = npop(tps);
930 		x = npop(tps);
931 		npush(tps, y ? (x / y) : 0);
932 		break;
933 
934 	    case 'm':
935 		y = npop(tps);
936 		x = npop(tps);
937 		npush(tps, y ? (x % y) : 0);
938 		break;
939 
940 	    case 'A':
941 		y = npop(tps);
942 		x = npop(tps);
943 		npush(tps, y && x);
944 		break;
945 
946 	    case 'O':
947 		y = npop(tps);
948 		x = npop(tps);
949 		npush(tps, y || x);
950 		break;
951 
952 	    case '&':
953 		y = npop(tps);
954 		x = npop(tps);
955 		npush(tps, x & y);
956 		break;
957 
958 	    case '|':
959 		y = npop(tps);
960 		x = npop(tps);
961 		npush(tps, x | y);
962 		break;
963 
964 	    case '^':
965 		y = npop(tps);
966 		x = npop(tps);
967 		npush(tps, x ^ y);
968 		break;
969 
970 	    case '=':
971 		y = npop(tps);
972 		x = npop(tps);
973 		npush(tps, x == y);
974 		break;
975 
976 	    case '<':
977 		y = npop(tps);
978 		x = npop(tps);
979 		npush(tps, x < y);
980 		break;
981 
982 	    case '>':
983 		y = npop(tps);
984 		x = npop(tps);
985 		npush(tps, x > y);
986 		break;
987 
988 	    case '!':
989 		x = npop(tps);
990 		npush(tps, !x);
991 		break;
992 
993 	    case '~':
994 		x = npop(tps);
995 		npush(tps, ~x);
996 		break;
997 
998 	    case 'i':
999 		/*
1000 		 * Increment the first two parameters -- if they are numbers
1001 		 * rather than strings.  As a side effect, assign into the
1002 		 * stack; if this is termcap, then the stack was populated
1003 		 * using the termcap hack above rather than via the terminfo
1004 		 * 'p' case.
1005 		 */
1006 		if (!incremented_two) {
1007 		    incremented_two = TRUE;
1008 		    if (data->p_is_s[0] == 0) {
1009 			data->param[0]++;
1010 			if (termcap_hack)
1011 			    TPS(stack)[0].data.num = (int) data->param[0];
1012 		    }
1013 		    if (data->p_is_s[1] == 0) {
1014 			data->param[1]++;
1015 			if (termcap_hack)
1016 			    TPS(stack)[1].data.num = (int) data->param[1];
1017 		    }
1018 		}
1019 		break;
1020 
1021 	    case '?':
1022 		break;
1023 
1024 	    case 't':
1025 		x = npop(tps);
1026 		if (!x) {
1027 		    /* scan forward for %e or %; at level zero */
1028 		    cp++;
1029 		    level = 0;
1030 		    while (*cp) {
1031 			if (*cp == '%') {
1032 			    cp++;
1033 			    if (*cp == '?')
1034 				level++;
1035 			    else if (*cp == ';') {
1036 				if (level > 0)
1037 				    level--;
1038 				else
1039 				    break;
1040 			    } else if (*cp == 'e' && level == 0)
1041 				break;
1042 			}
1043 
1044 			if (*cp)
1045 			    cp++;
1046 		    }
1047 		}
1048 		break;
1049 
1050 	    case 'e':
1051 		/* scan forward for a %; at level zero */
1052 		cp++;
1053 		level = 0;
1054 		while (*cp) {
1055 		    if (*cp == '%') {
1056 			cp++;
1057 			if (*cp == '?')
1058 			    level++;
1059 			else if (*cp == ';') {
1060 			    if (level > 0)
1061 				level--;
1062 			    else
1063 				break;
1064 			}
1065 		    }
1066 
1067 		    if (*cp)
1068 			cp++;
1069 		}
1070 		break;
1071 
1072 	    case ';':
1073 		break;
1074 
1075 	    }			/* endswitch (*cp) */
1076 	}			/* endelse (*cp == '%') */
1077 
1078 	if (*cp == '\0')
1079 	    break;
1080 
1081 	cp++;
1082     }				/* endwhile (*cp) */
1083 
1084     get_space(tps, (size_t) 1);
1085     TPS(out_buff)[TPS(out_used)] = '\0';
1086 
1087     if (TPS(stack_ptr) && !_nc_tparm_err) {
1088 	DEBUG(2, ("tparm: stack has %d item%s on return",
1089 		  TPS(stack_ptr),
1090 		  TPS(stack_ptr) == 1 ? "" : "s"));
1091 	_nc_tparm_err++;
1092     }
1093 
1094     T((T_RETURN("%s"), _nc_visbuf(TPS(out_buff))));
1095     return (TPS(out_buff));
1096 }
1097 
1098 #ifdef CUR
1099 /*
1100  * Only a few standard capabilities accept string parameters.  The others that
1101  * are parameterized accept only numeric parameters.
1102  */
1103 static bool
check_string_caps(TPARM_DATA * data,const char * string)1104 check_string_caps(TPARM_DATA *data, const char *string)
1105 {
1106     bool result = FALSE;
1107 
1108 #define CHECK_CAP(name) (VALID_STRING(name) && !strcmp(name, string))
1109 
1110     /*
1111      * Disallow string parameters unless we can check them against a terminal
1112      * description.
1113      */
1114     if (cur_term != NULL) {
1115 	int want_type = 0;
1116 
1117 	if (CHECK_CAP(pkey_key))
1118 	    want_type = 2;	/* function key #1, type string #2 */
1119 	else if (CHECK_CAP(pkey_local))
1120 	    want_type = 2;	/* function key #1, execute string #2 */
1121 	else if (CHECK_CAP(pkey_xmit))
1122 	    want_type = 2;	/* function key #1, transmit string #2 */
1123 	else if (CHECK_CAP(plab_norm))
1124 	    want_type = 2;	/* label #1, show string #2 */
1125 	else if (CHECK_CAP(pkey_plab))
1126 	    want_type = 6;	/* function key #1, type string #2, show string #3 */
1127 #if NCURSES_XNAMES
1128 	else {
1129 	    char *check;
1130 
1131 	    check = tigetstr("Cs");
1132 	    if (CHECK_CAP(check))
1133 		want_type = 1;	/* style #1 */
1134 
1135 	    check = tigetstr("Ms");
1136 	    if (CHECK_CAP(check))
1137 		want_type = 3;	/* storage unit #1, content #2 */
1138 	}
1139 #endif
1140 
1141 	if (want_type == data->tparm_type) {
1142 	    result = TRUE;
1143 	} else {
1144 	    T(("unexpected string-parameter"));
1145 	}
1146     }
1147     return result;
1148 }
1149 
1150 #define ValidCap(allow_strings) (myData.tparm_type == 0 || \
1151 				 (allow_strings && \
1152 				  check_string_caps(&myData, string)))
1153 #else
1154 #define ValidCap(allow_strings) 1
1155 #endif
1156 
1157 #if NCURSES_TPARM_VARARGS
1158 
1159 NCURSES_EXPORT(char *)
tparm(const char * string,...)1160 tparm(const char *string, ...)
1161 {
1162     TPARM_STATE *tps = get_tparm_state(cur_term);
1163     TPARM_DATA myData;
1164     char *result = NULL;
1165 
1166     _nc_tparm_err = 0;
1167 #ifdef TRACE
1168     tps->tname = "tparm";
1169 #endif /* TRACE */
1170 
1171     if (tparm_setup(cur_term, string, &myData) == OK && ValidCap(TRUE)) {
1172 	va_list ap;
1173 
1174 	va_start(ap, string);
1175 	tparm_copy_valist(&myData, TRUE, ap);
1176 	va_end(ap);
1177 
1178 	result = tparam_internal(tps, string, &myData);
1179     }
1180     return result;
1181 }
1182 
1183 #else /* !NCURSES_TPARM_VARARGS */
1184 
1185 NCURSES_EXPORT(char *)
tparm(const char * string,TPARM_ARG a1,TPARM_ARG a2,TPARM_ARG a3,TPARM_ARG a4,TPARM_ARG a5,TPARM_ARG a6,TPARM_ARG a7,TPARM_ARG a8,TPARM_ARG a9)1186 tparm(const char *string,
1187       TPARM_ARG a1,
1188       TPARM_ARG a2,
1189       TPARM_ARG a3,
1190       TPARM_ARG a4,
1191       TPARM_ARG a5,
1192       TPARM_ARG a6,
1193       TPARM_ARG a7,
1194       TPARM_ARG a8,
1195       TPARM_ARG a9)
1196 {
1197     TPARM_STATE *tps = get_tparm_state(cur_term);
1198     TPARM_DATA myData;
1199     char *result = NULL;
1200 
1201     _nc_tparm_err = 0;
1202 #ifdef TRACE
1203     tps->tname = "tparm";
1204 #endif /* TRACE */
1205 
1206 #define string_ok (sizeof(char*) <= sizeof(TPARM_ARG))
1207 
1208     if (tparm_setup(cur_term, string, &myData) == OK && ValidCap(string_ok)) {
1209 
1210 	myData.param[0] = a1;
1211 	myData.param[1] = a2;
1212 	myData.param[2] = a3;
1213 	myData.param[3] = a4;
1214 	myData.param[4] = a5;
1215 	myData.param[5] = a6;
1216 	myData.param[6] = a7;
1217 	myData.param[7] = a8;
1218 	myData.param[8] = a9;
1219 
1220 	result = tparam_internal(tps, string, &myData);
1221     }
1222     return result;
1223 }
1224 
1225 #endif /* NCURSES_TPARM_VARARGS */
1226 
1227 NCURSES_EXPORT(char *)
tiparm(const char * string,...)1228 tiparm(const char *string, ...)
1229 {
1230     TPARM_STATE *tps = get_tparm_state(cur_term);
1231     TPARM_DATA myData;
1232     char *result = NULL;
1233 
1234     _nc_tparm_err = 0;
1235 #ifdef TRACE
1236     tps->tname = "tiparm";
1237 #endif /* TRACE */
1238 
1239     if (tparm_setup(cur_term, string, &myData) == OK && ValidCap(TRUE)) {
1240 	va_list ap;
1241 
1242 	va_start(ap, string);
1243 	tparm_copy_valist(&myData, FALSE, ap);
1244 	va_end(ap);
1245 
1246 	result = tparam_internal(tps, string, &myData);
1247     }
1248     return result;
1249 }
1250 
1251 /*
1252  * Use tparm if the formatting string matches the expected number of parameters
1253  * counting string-parameters.
1254  */
1255 NCURSES_EXPORT(char *)
tiparm_s(int num_expected,int tparm_type,const char * string,...)1256 tiparm_s(int num_expected, int tparm_type, const char *string, ...)
1257 {
1258     TPARM_STATE *tps = get_tparm_state(cur_term);
1259     TPARM_DATA myData;
1260     char *result = NULL;
1261 
1262     _nc_tparm_err = 0;
1263 #ifdef TRACE
1264     tps->tname = "tiparm_s";
1265 #endif /* TRACE */
1266     if (num_expected >= 0 &&
1267 	num_expected <= 9 &&
1268 	tparm_type >= 0 &&
1269 	tparm_type < 7 &&	/* limit to 2 string parameters */
1270 	tparm_setup(cur_term, string, &myData) == OK &&
1271 	myData.tparm_type == tparm_type &&
1272 	myData.num_actual == num_expected) {
1273 	va_list ap;
1274 
1275 	va_start(ap, string);
1276 	tparm_copy_valist(&myData, FALSE, ap);
1277 	va_end(ap);
1278 
1279 	result = tparam_internal(tps, string, &myData);
1280     }
1281     return result;
1282 }
1283 
1284 /*
1285  * Analyze the formatting string, return the analysis.
1286  */
1287 NCURSES_EXPORT(int)
tiscan_s(int * num_expected,int * tparm_type,const char * string)1288 tiscan_s(int *num_expected, int *tparm_type, const char *string)
1289 {
1290     TPARM_DATA myData;
1291     int result = ERR;
1292 
1293 #ifdef TRACE
1294     TPARM_STATE *tps = get_tparm_state(cur_term);
1295     tps->tname = "tiscan_s";
1296 #endif /* TRACE */
1297 
1298     if (tparm_setup(cur_term, string, &myData) == OK) {
1299 	*num_expected = myData.num_actual;
1300 	*tparm_type = myData.tparm_type;
1301 	result = OK;
1302     }
1303     return result;
1304 }
1305 
1306 /*
1307  * The internal-use flavor ensures that parameters are numbers, not strings.
1308  * In addition to ensuring that they are numbers, it ensures that the parameter
1309  * count is consistent with intended usage.
1310  *
1311  * Unlike the general-purpose tparm/tiparm, these internal calls are fairly
1312  * well defined:
1313  *
1314  * expected == 0 - not applicable
1315  * expected == 1 - set color, or vertical/horizontal addressing
1316  * expected == 2 - cursor addressing
1317  * expected == 4 - initialize color or color pair
1318  * expected == 9 - set attributes
1319  *
1320  * Only for the last case (set attributes) should a parameter be optional.
1321  * Also, a capability which calls for more parameters than expected should be
1322  * ignored.
1323  *
1324  * Return a null if the parameter-checks fail.  Otherwise, return a pointer to
1325  * the formatted capability string.
1326  */
1327 NCURSES_EXPORT(char *)
_nc_tiparm(int expected,const char * string,...)1328 _nc_tiparm(int expected, const char *string, ...)
1329 {
1330     TPARM_STATE *tps = get_tparm_state(cur_term);
1331     TPARM_DATA myData;
1332     char *result = NULL;
1333 
1334     _nc_tparm_err = 0;
1335     T((T_CALLED("_nc_tiparm(%d, %s, ...)"), expected, _nc_visbuf(string)));
1336 #ifdef TRACE
1337     tps->tname = "_nc_tiparm";
1338 #endif /* TRACE */
1339 
1340     if (tparm_setup(cur_term, string, &myData) == OK && ValidCap(FALSE)) {
1341 #ifdef CUR
1342 	if (myData.num_actual != expected && cur_term != NULL) {
1343 	    int needed = expected;
1344 	    if (CHECK_CAP(to_status_line)) {
1345 		needed = 0;	/* allow for xterm's status line */
1346 	    } else if (CHECK_CAP(set_a_background)) {
1347 		needed = 0;	/* allow for monochrome fakers */
1348 	    } else if (CHECK_CAP(set_a_foreground)) {
1349 		needed = 0;
1350 	    } else if (CHECK_CAP(set_background)) {
1351 		needed = 0;
1352 	    } else if (CHECK_CAP(set_foreground)) {
1353 		needed = 0;
1354 	    }
1355 #if NCURSES_XNAMES
1356 	    else {
1357 		char *check;
1358 
1359 		check = tigetstr("xm");
1360 		if (CHECK_CAP(check)) {
1361 		    needed = 3;
1362 		}
1363 		check = tigetstr("S0");
1364 		if (CHECK_CAP(check)) {
1365 		    needed = 0;	/* used in screen-base */
1366 		}
1367 	    }
1368 #endif
1369 	    if (myData.num_actual >= needed && myData.num_actual <= expected)
1370 		expected = myData.num_actual;
1371 	}
1372 #endif
1373 	if (myData.num_actual == 0 && expected) {
1374 	    T(("missing parameter%s, expected %s%d",
1375 	       expected > 1 ? "s" : "",
1376 	       expected == 9 ? "up to " : "",
1377 	       expected));
1378 	} else if (myData.num_actual > expected) {
1379 	    T(("too many parameters, have %d, expected %d",
1380 	       myData.num_actual,
1381 	       expected));
1382 	} else if (expected != 9 && myData.num_actual != expected) {
1383 	    T(("expected %d parameters, have %d",
1384 	       myData.num_actual,
1385 	       expected));
1386 	} else {
1387 	    va_list ap;
1388 
1389 	    va_start(ap, string);
1390 	    tparm_copy_valist(&myData, FALSE, ap);
1391 	    va_end(ap);
1392 
1393 	    result = tparam_internal(tps, string, &myData);
1394 	}
1395     }
1396     returnPtr(result);
1397 }
1398 
1399 /*
1400  * Improve tic's checks by resetting the terminfo "static variables" before
1401  * calling functions which may update them.
1402  */
1403 NCURSES_EXPORT(void)
_nc_reset_tparm(TERMINAL * term)1404 _nc_reset_tparm(TERMINAL *term)
1405 {
1406     TPARM_STATE *tps = get_tparm_state(term);
1407     memset(TPS(static_vars), 0, sizeof(TPS(static_vars)));
1408 }
1409