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
56MODULE_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
118NCURSES_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
132typedef 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
145static int which_tparm;
146static TPARM_DATA **delete_tparm;
147#endif /* HAVE_TSEARCH */
148
149static char dummy[] = "";	/* avoid const-cast */
150
151#if HAVE_TSEARCH
152static int
153cmp_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
162static void
163visit_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
173NCURSES_EXPORT(void)
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
207static int
208tparm_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
226static 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
245static 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
264static 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
279static 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
299static 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
319static 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
336static 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
353static NCURSES_INLINE char *
354  (spop) (TPARM_STATE *tps) {
355    return spop(tps);
356}
357#undef spop
358#endif
359
360static NCURSES_INLINE const char *
361parse_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 */
466NCURSES_EXPORT(int)
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 */
601static int
602tparm_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 */
687static void
688tparm_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 */
714static bool
715tparm_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
737static void
738tparm_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
770static NCURSES_INLINE char *
771tparam_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 */
1101static bool
1102check_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
1159NCURSES_EXPORT(char *)
1160tparm(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
1185NCURSES_EXPORT(char *)
1186tparm(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
1227NCURSES_EXPORT(char *)
1228tiparm(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 */
1255NCURSES_EXPORT(char *)
1256tiparm_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 */
1287NCURSES_EXPORT(int)
1288tiscan_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 */
1327NCURSES_EXPORT(char *)
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 */
1403NCURSES_EXPORT(void)
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