xref: /freebsd/contrib/ncurses/ncurses/tinfo/lib_tparm.c (revision 21817992b3314c908ab50f0bb88d2ee750b9c4ac)
1 /****************************************************************************
2  * Copyright 2018-2021,2023 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 #define entry _ncu_entry
42 #define ENTRY _ncu_ENTRY
43 
44 #include <curses.priv.h>
45 
46 #undef entry
47 #undef ENTRY
48 
49 #if HAVE_TSEARCH
50 #include <search.h>
51 #endif
52 
53 #include <ctype.h>
54 #include <tic.h>
55 
56 MODULE_ID("$Id: lib_tparm.c,v 1.153 2023/11/04 19:28:41 tom Exp $")
57 
58 /*
59  *	char *
60  *	tparm(string, ...)
61  *
62  *	Substitute the given parameters into the given string by the following
63  *	rules (taken from terminfo(5)):
64  *
65  *	     Cursor addressing and other strings  requiring  parame-
66  *	ters in the terminal are described by a parameterized string
67  *	capability, with escapes like %x in  it.   For  example,  to
68  *	address  the  cursor, the cup capability is given, using two
69  *	parameters: the row and column to  address  to.   (Rows  and
70  *	columns  are  numbered  from  zero and refer to the physical
71  *	screen visible to the user, not to any  unseen  memory.)  If
72  *	the terminal has memory relative cursor addressing, that can
73  *	be indicated by
74  *
75  *	     The parameter mechanism uses  a  stack  and  special  %
76  *	codes  to manipulate it.  Typically a sequence will push one
77  *	of the parameters onto the stack and then print it  in  some
78  *	format.  Often more complex operations are necessary.
79  *
80  *	     The % encodings have the following meanings:
81  *
82  *	     %%        outputs `%'
83  *	     %c        print pop() like %c in printf()
84  *	     %s        print pop() like %s in printf()
85  *           %[[:]flags][width[.precision]][doxXs]
86  *                     as in printf, flags are [-+#] and space
87  *                     The ':' is used to avoid making %+ or %-
88  *                     patterns (see below).
89  *
90  *	     %p[1-9]   push ith parm
91  *	     %P[a-z]   set dynamic variable [a-z] to pop()
92  *	     %g[a-z]   get dynamic variable [a-z] and push it
93  *	     %P[A-Z]   set static variable [A-Z] to pop()
94  *	     %g[A-Z]   get static variable [A-Z] and push it
95  *	     %l        push strlen(pop)
96  *	     %'c'      push char constant c
97  *	     %{nn}     push integer constant nn
98  *
99  *	     %+ %- %* %/ %m
100  *	               arithmetic (%m is mod): push(pop() op pop())
101  *	     %& %| %^  bit operations: push(pop() op pop())
102  *	     %= %> %<  logical operations: push(pop() op pop())
103  *	     %A %O     logical and & or operations for conditionals
104  *	     %! %~     unary operations push(op pop())
105  *	     %i        add 1 to first two parms (for ANSI terminals)
106  *
107  *	     %? expr %t thenpart %e elsepart %;
108  *	               if-then-else, %e elsepart is optional.
109  *	               else-if's are possible ala Algol 68:
110  *	               %? c1 %t b1 %e c2 %t b2 %e c3 %t b3 %e c4 %t b4 %e b5 %;
111  *
112  *	For those of the above operators which are binary and not commutative,
113  *	the stack works in the usual way, with
114  *			%gx %gy %m
115  *	resulting in x mod y, not the reverse.
116  */
117 
118 NCURSES_EXPORT_VAR(int) _nc_tparm_err = 0;
119 
120 #define TPS(var) tps->var
121 #define popcount _nc_popcount	/* workaround for NetBSD 6.0 defect */
122 
123 #define get_tparm_state(term) \
124 	    (term != NULL \
125 	      ? &(term->tparm_state) \
126 	      : &(_nc_prescreen.tparm_state))
127 
128 #define isUPPER(c) ((c) >= 'A' && (c) <= 'Z')
129 #define isLOWER(c) ((c) >= 'a' && (c) <= 'z')
130 #define tc_BUMP()  if (level < 0 && number < 2) number++
131 
132 typedef struct {
133     const char *format;		/* format-string can be used as cache-key */
134     int tparm_type;		/* bit-set for each string-parameter */
135     int num_actual;
136     int num_parsed;
137     int num_popped;
138     TPARM_ARG param[NUM_PARM];
139     char *p_is_s[NUM_PARM];
140 } TPARM_DATA;
141 
142 #if HAVE_TSEARCH
143 #define MyCache _nc_globals.cached_tparm
144 #define MyCount _nc_globals.count_tparm
145 static int which_tparm;
146 static TPARM_DATA **delete_tparm;
147 #endif /* HAVE_TSEARCH */
148 
149 static char dummy[] = "";	/* avoid const-cast */
150 
151 #if HAVE_TSEARCH
152 static int
cmp_format(const void * p,const void * q)153 cmp_format(const void *p, const void *q)
154 {
155     const char *a = *(char *const *) p;
156     const char *b = *(char *const *) q;
157     return strcmp(a, b);
158 }
159 #endif
160 
161 #if HAVE_TSEARCH
162 static void
visit_nodes(const void * nodep,VISIT which,int depth)163 visit_nodes(const void *nodep, VISIT which, int depth)
164 {
165     (void) depth;
166     if (which == preorder || which == leaf) {
167 	delete_tparm[which_tparm] = *(TPARM_DATA **) nodep;
168 	which_tparm++;
169     }
170 }
171 #endif
172 
173 NCURSES_EXPORT(void)
_nc_free_tparm(TERMINAL * termp)174 _nc_free_tparm(TERMINAL *termp)
175 {
176     TPARM_STATE *tps = get_tparm_state(termp);
177 #if HAVE_TSEARCH
178     if (MyCount != 0) {
179 	delete_tparm = typeCalloc(TPARM_DATA *, MyCount);
180 	if (delete_tparm != NULL) {
181 	    which_tparm = 0;
182 	    twalk(MyCache, visit_nodes);
183 	    for (which_tparm = 0; which_tparm < MyCount; ++which_tparm) {
184 		TPARM_DATA *ptr = delete_tparm[which_tparm];
185 		if (ptr != NULL) {
186 		    tdelete(ptr, &MyCache, cmp_format);
187 		    free((char *) ptr->format);
188 		    free(ptr);
189 		}
190 	    }
191 	    which_tparm = 0;
192 	    twalk(MyCache, visit_nodes);
193 	    FreeAndNull(delete_tparm);
194 	}
195 	MyCount = 0;
196 	which_tparm = 0;
197     }
198 #endif
199     FreeAndNull(TPS(out_buff));
200     TPS(out_size) = 0;
201     TPS(out_used) = 0;
202 
203     FreeAndNull(TPS(fmt_buff));
204     TPS(fmt_size) = 0;
205 }
206 
207 static int
tparm_error(TPARM_STATE * tps,const char * message)208 tparm_error(TPARM_STATE *tps, const char *message)
209 {
210     (void) tps;
211     (void) message;
212     DEBUG(2, ("%s: %s", message, _nc_visbuf(TPS(tparam_base))));
213     return ++_nc_tparm_err;
214 }
215 
216 #define get_space(tps, need) \
217 { \
218     size_t need2get = need + TPS(out_used); \
219     if (need2get > TPS(out_size)) { \
220 	TPS(out_size) = need2get * 2; \
221 	TYPE_REALLOC(char, TPS(out_size), TPS(out_buff)); \
222     } \
223 }
224 
225 #if NCURSES_EXPANDED
226 static NCURSES_INLINE void
227   (get_space) (TPARM_STATE *tps, size_t need) {
228     get_space(tps, need);
229 }
230 
231 #undef get_space
232 #endif
233 
234 #define save_text(tps, fmt, s, len) \
235 { \
236     size_t s_len = (size_t) len + strlen(s) + strlen(fmt); \
237     get_space(tps, s_len + 1); \
238     _nc_SPRINTF(TPS(out_buff) + TPS(out_used), \
239 		_nc_SLIMIT(TPS(out_size) - TPS(out_used)) \
240 		fmt, s); \
241     TPS(out_used) += strlen(TPS(out_buff) + TPS(out_used)); \
242 }
243 
244 #if NCURSES_EXPANDED
245 static NCURSES_INLINE void
246   (save_text) (TPARM_STATE *tps, const char *fmt, const char *s, int len) {
247     save_text(tps, fmt, s, len);
248 }
249 
250 #undef save_text
251 #endif
252 
253 #define save_number(tps, fmt, number, len) \
254 { \
255     size_t s_len = (size_t) len + 30 + strlen(fmt); \
256     get_space(tps, s_len + 1); \
257     _nc_SPRINTF(TPS(out_buff) + TPS(out_used), \
258 		_nc_SLIMIT(TPS(out_size) - TPS(out_used)) \
259 		fmt, number); \
260     TPS(out_used) += strlen(TPS(out_buff) + TPS(out_used)); \
261 }
262 
263 #if NCURSES_EXPANDED
264 static NCURSES_INLINE void
265   (save_number) (TPARM_STATE *tps, const char *fmt, int number, int len) {
266     save_number(tps, fmt, number, len);
267 }
268 
269 #undef save_number
270 #endif
271 
272 #define save_char(tps, c) \
273 { \
274     get_space(tps, (size_t) 1); \
275     TPS(out_buff)[TPS(out_used)++] = (char) ((c == 0) ? 0200 : c); \
276 }
277 
278 #if NCURSES_EXPANDED
279 static NCURSES_INLINE void
280   (save_char) (TPARM_STATE *tps, int c) {
281     save_char(tps, c);
282 }
283 
284 #undef save_char
285 #endif
286 
287 #define npush(tps, x) \
288 { \
289     if (TPS(stack_ptr) < STACKSIZE) { \
290 	TPS(stack)[TPS(stack_ptr)].num_type = TRUE; \
291 	TPS(stack)[TPS(stack_ptr)].data.num = x; \
292 	TPS(stack_ptr)++; \
293     } else { \
294 	(void) tparm_error(tps, "npush: stack overflow"); \
295     } \
296 }
297 
298 #if NCURSES_EXPANDED
299 static NCURSES_INLINE void
300   (npush) (TPARM_STATE *tps, int x) {
301     npush(tps, x);
302 }
303 
304 #undef npush
305 #endif
306 
307 #define spush(tps, x) \
308 { \
309     if (TPS(stack_ptr) < STACKSIZE) { \
310 	TPS(stack)[TPS(stack_ptr)].num_type = FALSE; \
311 	TPS(stack)[TPS(stack_ptr)].data.str = x; \
312 	TPS(stack_ptr)++; \
313     } else { \
314 	(void) tparm_error(tps, "spush: stack overflow"); \
315     } \
316 }
317 
318 #if NCURSES_EXPANDED
319 static NCURSES_INLINE void
320   (spush) (TPARM_STATE *tps, char *x) {
321     spush(tps, x);
322 }
323 
324 #undef spush
325 #endif
326 
327 #define npop(tps) \
328     ((TPS(stack_ptr)-- > 0) \
329      ? ((TPS(stack)[TPS(stack_ptr)].num_type) \
330 	 ? TPS(stack)[TPS(stack_ptr)].data.num \
331 	 : 0) \
332      : (tparm_error(tps, "npop: stack underflow"), \
333         TPS(stack_ptr) = 0))
334 
335 #if NCURSES_EXPANDED
336 static NCURSES_INLINE int
337   (npop) (TPARM_STATE *tps) {
338     return npop(tps);
339 }
340 #undef npop
341 #endif
342 
343 #define spop(tps) \
344     ((TPS(stack_ptr)-- > 0) \
345      ? ((!TPS(stack)[TPS(stack_ptr)].num_type \
346         && TPS(stack)[TPS(stack_ptr)].data.str != 0) \
347          ? TPS(stack)[TPS(stack_ptr)].data.str \
348          : dummy) \
349      : (tparm_error(tps, "spop: stack underflow"), \
350         dummy))
351 
352 #if NCURSES_EXPANDED
353 static NCURSES_INLINE char *
354   (spop) (TPARM_STATE *tps) {
355     return spop(tps);
356 }
357 #undef spop
358 #endif
359 
360 static NCURSES_INLINE const char *
parse_format(const char * s,char * format,int * len)361 parse_format(const char *s, char *format, int *len)
362 {
363     *len = 0;
364     if (format != 0) {
365 	bool done = FALSE;
366 	bool allowminus = FALSE;
367 	bool dot = FALSE;
368 	bool err = FALSE;
369 	char *fmt = format;
370 	int my_width = 0;
371 	int my_prec = 0;
372 	int value = 0;
373 
374 	*len = 0;
375 	*format++ = '%';
376 	while (*s != '\0' && !done) {
377 	    switch (*s) {
378 	    case 'c':		/* FALLTHRU */
379 	    case 'd':		/* FALLTHRU */
380 	    case 'o':		/* FALLTHRU */
381 	    case 'x':		/* FALLTHRU */
382 	    case 'X':		/* FALLTHRU */
383 	    case 's':
384 #ifdef EXP_XTERM_1005
385 	    case 'u':
386 #endif
387 		*format++ = *s;
388 		done = TRUE;
389 		break;
390 	    case '.':
391 		*format++ = *s++;
392 		if (dot) {
393 		    err = TRUE;
394 		} else {	/* value before '.' is the width */
395 		    dot = TRUE;
396 		    my_width = value;
397 		}
398 		value = 0;
399 		break;
400 	    case '#':
401 		*format++ = *s++;
402 		break;
403 	    case ' ':
404 		*format++ = *s++;
405 		break;
406 	    case ':':
407 		s++;
408 		allowminus = TRUE;
409 		break;
410 	    case '-':
411 		if (allowminus) {
412 		    *format++ = *s++;
413 		} else {
414 		    done = TRUE;
415 		}
416 		break;
417 	    default:
418 		if (isdigit(UChar(*s))) {
419 		    value = (value * 10) + (*s - '0');
420 		    if (value > 10000)
421 			err = TRUE;
422 		    *format++ = *s++;
423 		} else {
424 		    done = TRUE;
425 		}
426 	    }
427 	}
428 
429 	/*
430 	 * If we found an error, ignore (and remove) the flags.
431 	 */
432 	if (err) {
433 	    my_width = my_prec = value = 0;
434 	    format = fmt;
435 	    *format++ = '%';
436 	    *format++ = *s;
437 	}
438 
439 	/*
440 	 * Any value after '.' is the precision.  If we did not see '.', then
441 	 * the value is the width.
442 	 */
443 	if (dot)
444 	    my_prec = value;
445 	else
446 	    my_width = value;
447 
448 	*format = '\0';
449 	/* return maximum string length in print */
450 	*len = (my_width > my_prec) ? my_width : my_prec;
451     }
452     return s;
453 }
454 
455 /*
456  * Analyze the string to see how many parameters we need from the varargs list,
457  * and what their types are.  We will only accept string parameters if they
458  * appear as a %l or %s format following an explicit parameter reference (e.g.,
459  * %p2%s).  All other parameters are numbers.
460  *
461  * 'number' counts coarsely the number of pop's we see in the string, and
462  * 'popcount' shows the highest parameter number in the string.  We would like
463  * to simply use the latter count, but if we are reading termcap strings, there
464  * may be cases that we cannot see the explicit parameter numbers.
465  */
466 NCURSES_EXPORT(int)
_nc_tparm_analyze(TERMINAL * term,const char * string,char ** p_is_s,int * popcount)467 _nc_tparm_analyze(TERMINAL *term, const char *string, char **p_is_s, int *popcount)
468 {
469     TPARM_STATE *tps = get_tparm_state(term);
470     size_t len2;
471     int i;
472     int lastpop = -1;
473     int len;
474     int number = 0;
475     int level = -1;
476     const char *cp = string;
477 
478     if (cp == 0)
479 	return 0;
480 
481     if ((len2 = strlen(cp)) + 2 > TPS(fmt_size)) {
482 	TPS(fmt_size) += len2 + 2;
483 	TPS(fmt_buff) = typeRealloc(char, TPS(fmt_size), TPS(fmt_buff));
484 	if (TPS(fmt_buff) == 0)
485 	    return 0;
486     }
487 
488     memset(p_is_s, 0, sizeof(p_is_s[0]) * NUM_PARM);
489     *popcount = 0;
490 
491     while ((cp - string) < (int) len2) {
492 	if (*cp == '%') {
493 	    cp++;
494 	    cp = parse_format(cp, TPS(fmt_buff), &len);
495 	    switch (*cp) {
496 	    default:
497 		break;
498 
499 	    case 'd':		/* FALLTHRU */
500 	    case 'o':		/* FALLTHRU */
501 	    case 'x':		/* FALLTHRU */
502 	    case 'X':		/* FALLTHRU */
503 	    case 'c':		/* FALLTHRU */
504 #ifdef EXP_XTERM_1005
505 	    case 'u':
506 #endif
507 		if (lastpop <= 0) {
508 		    tc_BUMP();
509 		}
510 		level -= 1;
511 		lastpop = -1;
512 		break;
513 
514 	    case 'l':
515 	    case 's':
516 		if (lastpop > 0) {
517 		    level -= 1;
518 		    p_is_s[lastpop - 1] = dummy;
519 		}
520 		tc_BUMP();
521 		break;
522 
523 	    case 'p':
524 		cp++;
525 		i = (UChar(*cp) - '0');
526 		if (i >= 0 && i <= NUM_PARM) {
527 		    ++level;
528 		    lastpop = i;
529 		    if (lastpop > *popcount)
530 			*popcount = lastpop;
531 		}
532 		break;
533 
534 	    case 'P':
535 		++cp;
536 		break;
537 
538 	    case 'g':
539 		++level;
540 		cp++;
541 		break;
542 
543 	    case S_QUOTE:
544 		++level;
545 		cp += 2;
546 		lastpop = -1;
547 		break;
548 
549 	    case L_BRACE:
550 		++level;
551 		cp++;
552 		while (isdigit(UChar(*cp))) {
553 		    cp++;
554 		}
555 		break;
556 
557 	    case '+':
558 	    case '-':
559 	    case '*':
560 	    case '/':
561 	    case 'm':
562 	    case 'A':
563 	    case 'O':
564 	    case '&':
565 	    case '|':
566 	    case '^':
567 	    case '=':
568 	    case '<':
569 	    case '>':
570 		tc_BUMP();
571 		level -= 1;	/* pop 2, operate, push 1 */
572 		lastpop = -1;
573 		break;
574 
575 	    case '!':
576 	    case '~':
577 		tc_BUMP();
578 		lastpop = -1;
579 		break;
580 
581 	    case 'i':
582 		/* will add 1 to first (usually two) parameters */
583 		break;
584 	    }
585 	}
586 	if (*cp != '\0')
587 	    cp++;
588     }
589 
590     if (number > NUM_PARM)
591 	number = NUM_PARM;
592     return number;
593 }
594 
595 /*
596  * Analyze the capability string, finding the number of parameters and their
597  * types.
598  *
599  * TODO: cache the result so that this is done once per capability per term.
600  */
601 static int
tparm_setup(TERMINAL * term,const char * string,TPARM_DATA * result)602 tparm_setup(TERMINAL *term, const char *string, TPARM_DATA *result)
603 {
604     TPARM_STATE *tps = get_tparm_state(term);
605     int rc = OK;
606 
607     TPS(out_used) = 0;
608     memset(result, 0, sizeof(*result));
609 
610     if (!VALID_STRING(string)) {
611 	TR(TRACE_CALLS, ("%s: format is invalid", TPS(tname)));
612 	rc = ERR;
613     } else {
614 #if HAVE_TSEARCH
615 	TPARM_DATA *fs;
616 	void *ft;
617 
618 	result->format = string;
619 	if ((ft = tfind(result, &MyCache, cmp_format)) != 0) {
620 	    size_t len2;
621 	    fs = *(TPARM_DATA **) ft;
622 	    *result = *fs;
623 	    if ((len2 = strlen(string)) + 2 > TPS(fmt_size)) {
624 		TPS(fmt_size) += len2 + 2;
625 		TPS(fmt_buff) = typeRealloc(char, TPS(fmt_size), TPS(fmt_buff));
626 		if (TPS(fmt_buff) == 0)
627 		    return ERR;
628 	    }
629 	} else
630 #endif
631 	{
632 	    /*
633 	     * Find the highest parameter-number referred to in the format
634 	     * string.  Use this value to limit the number of arguments copied
635 	     * from the variable-length argument list.
636 	     */
637 	    result->num_parsed = _nc_tparm_analyze(term, string,
638 						   result->p_is_s,
639 						   &(result->num_popped));
640 	    if (TPS(fmt_buff) == 0) {
641 		TR(TRACE_CALLS, ("%s: error in analysis", TPS(tname)));
642 		rc = ERR;
643 	    } else {
644 		int n;
645 
646 		if (result->num_parsed > NUM_PARM)
647 		    result->num_parsed = NUM_PARM;
648 		if (result->num_popped > NUM_PARM)
649 		    result->num_popped = NUM_PARM;
650 		result->num_actual = Max(result->num_popped, result->num_parsed);
651 
652 		for (n = 0; n < result->num_actual; ++n) {
653 		    if (result->p_is_s[n])
654 			result->tparm_type |= (1 << n);
655 		}
656 #if HAVE_TSEARCH
657 		if ((fs = typeCalloc(TPARM_DATA, 1)) != 0) {
658 		    *fs = *result;
659 		    if ((fs->format = strdup(string)) != 0) {
660 			if (tsearch(fs, &MyCache, cmp_format) != 0) {
661 			    ++MyCount;
662 			} else {
663 			    free(fs);
664 			    rc = ERR;
665 			}
666 		    } else {
667 			free(fs);
668 			rc = ERR;
669 		    }
670 		} else {
671 		    rc = ERR;
672 		}
673 #endif
674 	    }
675 	}
676     }
677 
678     return rc;
679 }
680 
681 /*
682  * A few caps (such as plab_norm) have string-valued parms.  We'll have to
683  * assume that the caller knows the difference, since a char* and an int may
684  * not be the same size on the stack.  The normal prototype for tparm uses 9
685  * long's, which is consistent with our va_arg() usage.
686  */
687 static void
tparm_copy_valist(TPARM_DATA * data,int use_TPARM_ARG,va_list ap)688 tparm_copy_valist(TPARM_DATA *data, int use_TPARM_ARG, va_list ap)
689 {
690     int i;
691 
692     for (i = 0; i < data->num_actual; i++) {
693 	if (data->p_is_s[i] != 0) {
694 	    char *value = va_arg(ap, char *);
695 	    if (value == 0)
696 		value = dummy;
697 	    data->p_is_s[i] = value;
698 	    data->param[i] = 0;
699 	} else if (use_TPARM_ARG) {
700 	    data->param[i] = va_arg(ap, TPARM_ARG);
701 	} else {
702 	    data->param[i] = (TPARM_ARG) va_arg(ap, int);
703 	}
704     }
705 }
706 
707 /*
708  * This is a termcap compatibility hack.  If there are no explicit pop
709  * operations in the string, load the stack in such a way that successive pops
710  * will grab successive parameters.  That will make the expansion of (for
711  * example) \E[%d;%dH work correctly in termcap style, which means tparam()
712  * will expand termcap strings OK.
713  */
714 static bool
tparm_tc_compat(TPARM_STATE * tps,TPARM_DATA * data)715 tparm_tc_compat(TPARM_STATE *tps, TPARM_DATA *data)
716 {
717     bool termcap_hack = FALSE;
718 
719     TPS(stack_ptr) = 0;
720 
721     if (data->num_popped == 0) {
722 	int i;
723 
724 	termcap_hack = TRUE;
725 	for (i = data->num_parsed - 1; i >= 0; i--) {
726 	    if (data->p_is_s[i]) {
727 		spush(tps, data->p_is_s[i]);
728 	    } else {
729 		npush(tps, (int) data->param[i]);
730 	    }
731 	}
732     }
733     return termcap_hack;
734 }
735 
736 #ifdef TRACE
737 static void
tparm_trace_call(TPARM_STATE * tps,const char * string,TPARM_DATA * data)738 tparm_trace_call(TPARM_STATE *tps, const char *string, TPARM_DATA *data)
739 {
740     if (USE_TRACEF(TRACE_CALLS)) {
741 	int i;
742 	for (i = 0; i < data->num_actual; i++) {
743 	    if (data->p_is_s[i] != 0) {
744 		save_text(tps, ", %s", _nc_visbuf(data->p_is_s[i]), 0);
745 	    } else if ((long) data->param[i] > MAX_OF_TYPE(NCURSES_INT2) ||
746 		       (long) data->param[i] < 0) {
747 		_tracef("BUG: problem with tparm parameter #%d of %d",
748 			i + 1, data->num_actual);
749 		break;
750 	    } else {
751 		save_number(tps, ", %d", (int) data->param[i], 0);
752 	    }
753 	}
754 	_tracef(T_CALLED("%s(%s%s)"), TPS(tname), _nc_visbuf(string), TPS(out_buff));
755 	TPS(out_used) = 0;
756 	_nc_unlock_global(tracef);
757     }
758 }
759 
760 #else
761 #define tparm_trace_call(tps, string, data)	/* nothing */
762 #endif /* TRACE */
763 
764 #define init_vars(name) \
765 	if (!name##_used) { \
766 	    name##_used = TRUE; \
767 	    memset(name##_vars, 0, sizeof(name##_vars)); \
768 	}
769 
770 static NCURSES_INLINE char *
tparam_internal(TPARM_STATE * tps,const char * string,TPARM_DATA * data)771 tparam_internal(TPARM_STATE *tps, const char *string, TPARM_DATA *data)
772 {
773     int number;
774     int len;
775     int level;
776     int x, y;
777     int i;
778     const char *s;
779     const char *cp = string;
780     size_t len2 = strlen(cp);
781     bool incremented_two = FALSE;
782     bool termcap_hack = tparm_tc_compat(tps, data);
783     /*
784      * SVr4 curses stores variables 'A' to 'Z' in the TERMINAL structure (so
785      * they are initialized once to zero), and variables 'a' to 'z' on the
786      * stack in tparm, referring to the former as "static" and the latter as
787      * "dynamic".  However, it makes no check to ensure that the "dynamic"
788      * variables are initialized.
789      *
790      * Solaris xpg4 curses makes no distinction between the upper/lower, and
791      * stores the common set of 26 variables on the stack, without initializing
792      * them.
793      *
794      * In ncurses, both sets of variables are initialized on the first use.
795      */
796     bool dynamic_used = FALSE;
797     int dynamic_vars[NUM_VARS];
798 
799     tparm_trace_call(tps, string, data);
800 
801     if (TPS(fmt_buff) == NULL) {
802 	T((T_RETURN("<null>")));
803 	return NULL;
804     }
805 
806     while ((cp - string) < (int) len2) {
807 	if (*cp != '%') {
808 	    save_char(tps, UChar(*cp));
809 	} else {
810 	    TPS(tparam_base) = cp++;
811 	    cp = parse_format(cp, TPS(fmt_buff), &len);
812 	    switch (*cp) {
813 	    default:
814 		break;
815 	    case '%':
816 		save_char(tps, '%');
817 		break;
818 
819 	    case 'd':		/* FALLTHRU */
820 	    case 'o':		/* FALLTHRU */
821 	    case 'x':		/* FALLTHRU */
822 	    case 'X':		/* FALLTHRU */
823 		x = npop(tps);
824 		save_number(tps, TPS(fmt_buff), x, len);
825 		break;
826 
827 	    case 'c':		/* FALLTHRU */
828 		x = npop(tps);
829 		save_char(tps, x);
830 		break;
831 
832 #ifdef EXP_XTERM_1005
833 	    case 'u':
834 		{
835 		    unsigned char target[10];
836 		    unsigned source = (unsigned) npop(tps);
837 		    int rc = _nc_conv_to_utf8(target, source, (unsigned)
838 					      sizeof(target));
839 		    int n;
840 		    for (n = 0; n < rc; ++n) {
841 			save_char(tps, target[n]);
842 		    }
843 		}
844 		break;
845 #endif
846 	    case 'l':
847 		s = spop(tps);
848 		npush(tps, (int) strlen(s));
849 		break;
850 
851 	    case 's':
852 		s = spop(tps);
853 		save_text(tps, TPS(fmt_buff), s, len);
854 		break;
855 
856 	    case 'p':
857 		cp++;
858 		i = (UChar(*cp) - '1');
859 		if (i >= 0 && i < NUM_PARM) {
860 		    if (data->p_is_s[i]) {
861 			spush(tps, data->p_is_s[i]);
862 		    } else {
863 			npush(tps, (int) data->param[i]);
864 		    }
865 		}
866 		break;
867 
868 	    case 'P':
869 		cp++;
870 		if (isUPPER(*cp)) {
871 		    i = (UChar(*cp) - 'A');
872 		    TPS(static_vars)[i] = npop(tps);
873 		} else if (isLOWER(*cp)) {
874 		    i = (UChar(*cp) - 'a');
875 		    init_vars(dynamic);
876 		    dynamic_vars[i] = npop(tps);
877 		}
878 		break;
879 
880 	    case 'g':
881 		cp++;
882 		if (isUPPER(*cp)) {
883 		    i = (UChar(*cp) - 'A');
884 		    npush(tps, TPS(static_vars)[i]);
885 		} else if (isLOWER(*cp)) {
886 		    i = (UChar(*cp) - 'a');
887 		    init_vars(dynamic);
888 		    npush(tps, dynamic_vars[i]);
889 		}
890 		break;
891 
892 	    case S_QUOTE:
893 		cp++;
894 		npush(tps, UChar(*cp));
895 		cp++;
896 		break;
897 
898 	    case L_BRACE:
899 		number = 0;
900 		cp++;
901 		while (isdigit(UChar(*cp))) {
902 		    number = (number * 10) + (UChar(*cp) - '0');
903 		    cp++;
904 		}
905 		npush(tps, number);
906 		break;
907 
908 	    case '+':
909 		y = npop(tps);
910 		x = npop(tps);
911 		npush(tps, x + y);
912 		break;
913 
914 	    case '-':
915 		y = npop(tps);
916 		x = npop(tps);
917 		npush(tps, x - y);
918 		break;
919 
920 	    case '*':
921 		y = npop(tps);
922 		x = npop(tps);
923 		npush(tps, x * y);
924 		break;
925 
926 	    case '/':
927 		y = npop(tps);
928 		x = npop(tps);
929 		npush(tps, y ? (x / y) : 0);
930 		break;
931 
932 	    case 'm':
933 		y = npop(tps);
934 		x = npop(tps);
935 		npush(tps, y ? (x % y) : 0);
936 		break;
937 
938 	    case 'A':
939 		y = npop(tps);
940 		x = npop(tps);
941 		npush(tps, y && x);
942 		break;
943 
944 	    case 'O':
945 		y = npop(tps);
946 		x = npop(tps);
947 		npush(tps, y || x);
948 		break;
949 
950 	    case '&':
951 		y = npop(tps);
952 		x = npop(tps);
953 		npush(tps, x & y);
954 		break;
955 
956 	    case '|':
957 		y = npop(tps);
958 		x = npop(tps);
959 		npush(tps, x | y);
960 		break;
961 
962 	    case '^':
963 		y = npop(tps);
964 		x = npop(tps);
965 		npush(tps, x ^ y);
966 		break;
967 
968 	    case '=':
969 		y = npop(tps);
970 		x = npop(tps);
971 		npush(tps, x == y);
972 		break;
973 
974 	    case '<':
975 		y = npop(tps);
976 		x = npop(tps);
977 		npush(tps, x < y);
978 		break;
979 
980 	    case '>':
981 		y = npop(tps);
982 		x = npop(tps);
983 		npush(tps, x > y);
984 		break;
985 
986 	    case '!':
987 		x = npop(tps);
988 		npush(tps, !x);
989 		break;
990 
991 	    case '~':
992 		x = npop(tps);
993 		npush(tps, ~x);
994 		break;
995 
996 	    case 'i':
997 		/*
998 		 * Increment the first two parameters -- if they are numbers
999 		 * rather than strings.  As a side effect, assign into the
1000 		 * stack; if this is termcap, then the stack was populated
1001 		 * using the termcap hack above rather than via the terminfo
1002 		 * 'p' case.
1003 		 */
1004 		if (!incremented_two) {
1005 		    incremented_two = TRUE;
1006 		    if (data->p_is_s[0] == 0) {
1007 			data->param[0]++;
1008 			if (termcap_hack)
1009 			    TPS(stack)[0].data.num = (int) data->param[0];
1010 		    }
1011 		    if (data->p_is_s[1] == 0) {
1012 			data->param[1]++;
1013 			if (termcap_hack)
1014 			    TPS(stack)[1].data.num = (int) data->param[1];
1015 		    }
1016 		}
1017 		break;
1018 
1019 	    case '?':
1020 		break;
1021 
1022 	    case 't':
1023 		x = npop(tps);
1024 		if (!x) {
1025 		    /* scan forward for %e or %; at level zero */
1026 		    cp++;
1027 		    level = 0;
1028 		    while (*cp) {
1029 			if (*cp == '%') {
1030 			    cp++;
1031 			    if (*cp == '?')
1032 				level++;
1033 			    else if (*cp == ';') {
1034 				if (level > 0)
1035 				    level--;
1036 				else
1037 				    break;
1038 			    } else if (*cp == 'e' && level == 0)
1039 				break;
1040 			}
1041 
1042 			if (*cp)
1043 			    cp++;
1044 		    }
1045 		}
1046 		break;
1047 
1048 	    case 'e':
1049 		/* scan forward for a %; at level zero */
1050 		cp++;
1051 		level = 0;
1052 		while (*cp) {
1053 		    if (*cp == '%') {
1054 			cp++;
1055 			if (*cp == '?')
1056 			    level++;
1057 			else if (*cp == ';') {
1058 			    if (level > 0)
1059 				level--;
1060 			    else
1061 				break;
1062 			}
1063 		    }
1064 
1065 		    if (*cp)
1066 			cp++;
1067 		}
1068 		break;
1069 
1070 	    case ';':
1071 		break;
1072 
1073 	    }			/* endswitch (*cp) */
1074 	}			/* endelse (*cp == '%') */
1075 
1076 	if (*cp == '\0')
1077 	    break;
1078 
1079 	cp++;
1080     }				/* endwhile (*cp) */
1081 
1082     get_space(tps, (size_t) 1);
1083     TPS(out_buff)[TPS(out_used)] = '\0';
1084 
1085     if (TPS(stack_ptr) && !_nc_tparm_err) {
1086 	DEBUG(2, ("tparm: stack has %d item%s on return",
1087 		  TPS(stack_ptr),
1088 		  TPS(stack_ptr) == 1 ? "" : "s"));
1089 	_nc_tparm_err++;
1090     }
1091 
1092     T((T_RETURN("%s"), _nc_visbuf(TPS(out_buff))));
1093     return (TPS(out_buff));
1094 }
1095 
1096 #ifdef CUR
1097 /*
1098  * Only a few standard capabilities accept string parameters.  The others that
1099  * are parameterized accept only numeric parameters.
1100  */
1101 static bool
check_string_caps(TPARM_DATA * data,const char * string)1102 check_string_caps(TPARM_DATA *data, const char *string)
1103 {
1104     bool result = FALSE;
1105 
1106 #define CHECK_CAP(name) (VALID_STRING(name) && !strcmp(name, string))
1107 
1108     /*
1109      * Disallow string parameters unless we can check them against a terminal
1110      * description.
1111      */
1112     if (cur_term != NULL) {
1113 	int want_type = 0;
1114 
1115 	if (CHECK_CAP(pkey_key))
1116 	    want_type = 2;	/* function key #1, type string #2 */
1117 	else if (CHECK_CAP(pkey_local))
1118 	    want_type = 2;	/* function key #1, execute string #2 */
1119 	else if (CHECK_CAP(pkey_xmit))
1120 	    want_type = 2;	/* function key #1, transmit string #2 */
1121 	else if (CHECK_CAP(plab_norm))
1122 	    want_type = 2;	/* label #1, show string #2 */
1123 #ifdef pkey_plab
1124 	else if (CHECK_CAP(pkey_plab))
1125 	    want_type = 6;	/* function key #1, type string #2, show string #3 */
1126 #endif
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