ncurses 6.3 - patch 20211120
[ncurses.git] / ncurses / tinfo / lib_tparm.c
1 /****************************************************************************
2  * Copyright 2018-2020,2021 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.137 2021/11/20 23:29:15 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
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
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)
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         which_tparm = 0;
181         twalk(MyCache, visit_nodes);
182         for (which_tparm = 0; which_tparm < MyCount; ++which_tparm) {
183             TPARM_DATA *ptr = delete_tparm[which_tparm];
184             if (ptr != NULL) {
185                 tdelete(ptr, &MyCache, cmp_format);
186                 free((char *) ptr->format);
187                 free(ptr);
188             }
189         }
190         which_tparm = 0;
191         twalk(MyCache, visit_nodes);
192         FreeAndNull(delete_tparm);
193         MyCount = 0;
194         which_tparm = 0;
195     }
196 #endif
197     FreeAndNull(TPS(out_buff));
198     TPS(out_size) = 0;
199     TPS(out_used) = 0;
200
201     FreeAndNull(TPS(fmt_buff));
202     TPS(fmt_size) = 0;
203 }
204
205 static int
206 tparm_error(TPARM_STATE *tps, const char *message)
207 {
208     (void) tps;
209     (void) message;
210     DEBUG(2, ("%s: %s", message, _nc_visbuf(TPS(tparam_base))));
211     return ++_nc_tparm_err;
212 }
213
214 #define get_space(tps, need) \
215 { \
216     size_t need2get = need + TPS(out_used); \
217     if (need2get > TPS(out_size)) { \
218         TPS(out_size) = need2get * 2; \
219         TYPE_REALLOC(char, TPS(out_size), TPS(out_buff)); \
220     } \
221 }
222
223 #if NCURSES_EXPANDED
224 static NCURSES_INLINE void
225   (get_space) (TPARM_STATE *tps, size_t need) {
226     get_space(tps, need);
227 }
228
229 #undef get_space
230 #endif
231
232 #define save_text(tps, fmt, s, len) \
233 { \
234     size_t s_len = (size_t) len + strlen(s) + strlen(fmt); \
235     get_space(tps, s_len + 1); \
236     _nc_SPRINTF(TPS(out_buff) + TPS(out_used), \
237                 _nc_SLIMIT(TPS(out_size) - TPS(out_used)) \
238                 fmt, s); \
239     TPS(out_used) += strlen(TPS(out_buff) + TPS(out_used)); \
240 }
241
242 #if NCURSES_EXPANDED
243 static NCURSES_INLINE void
244   (save_text) (TPARM_STATE *tps, const char *fmt, const char *s, int len) {
245     save_text(tps, fmt, s, len);
246 }
247
248 #undef save_text
249 #endif
250
251 #define save_number(tps, fmt, number, len) \
252 { \
253     size_t s_len = (size_t) len + 30 + strlen(fmt); \
254     get_space(tps, s_len + 1); \
255     _nc_SPRINTF(TPS(out_buff) + TPS(out_used), \
256                 _nc_SLIMIT(TPS(out_size) - TPS(out_used)) \
257                 fmt, number); \
258     TPS(out_used) += strlen(TPS(out_buff) + TPS(out_used)); \
259 }
260
261 #if NCURSES_EXPANDED
262 static NCURSES_INLINE void
263   (save_number) (TPARM_STATE *tps, const char *fmt, int number, int len) {
264     save_number(tps, fmt, number, len);
265 }
266
267 #undef save_number
268 #endif
269
270 #define save_char(tps, c) \
271 { \
272     get_space(tps, (size_t) 1); \
273     TPS(out_buff)[TPS(out_used)++] = (char) ((c == 0) ? 0200 : c); \
274 }
275
276 #if NCURSES_EXPANDED
277 static NCURSES_INLINE void
278   (save_char) (TPARM_STATE *tps, int c) {
279     save_char(tps, c);
280 }
281
282 #undef save_char
283 #endif
284
285 #define npush(tps, x) \
286 { \
287     if (TPS(stack_ptr) < STACKSIZE) { \
288         TPS(stack)[TPS(stack_ptr)].num_type = TRUE; \
289         TPS(stack)[TPS(stack_ptr)].data.num = x; \
290         TPS(stack_ptr)++; \
291     } else { \
292         (void) tparm_error(tps, "npush: stack overflow"); \
293     } \
294 }
295
296 #if NCURSES_EXPANDED
297 static NCURSES_INLINE void
298   (npush) (TPARM_STATE *tps, int x) {
299     npush(tps, x);
300 }
301
302 #undef npush
303 #endif
304
305 #define spush(tps, x) \
306 { \
307     if (TPS(stack_ptr) < STACKSIZE) { \
308         TPS(stack)[TPS(stack_ptr)].num_type = FALSE; \
309         TPS(stack)[TPS(stack_ptr)].data.str = x; \
310         TPS(stack_ptr)++; \
311     } else { \
312         (void) tparm_error(tps, "spush: stack overflow"); \
313     } \
314 }
315
316 #if NCURSES_EXPANDED
317 static NCURSES_INLINE void
318   (spush) (TPARM_STATE *tps, char *x) {
319     spush(tps, x);
320 }
321
322 #undef spush
323 #endif
324
325 #define npop(tps) \
326     ((TPS(stack_ptr)-- > 0) \
327      ? ((TPS(stack)[TPS(stack_ptr)].num_type) \
328          ? TPS(stack)[TPS(stack_ptr)].data.num \
329          : 0) \
330      : (tparm_error(tps, "npop: stack underflow"), \
331         TPS(stack_ptr) = 0))
332
333 #if NCURSES_EXPANDED
334 static NCURSES_INLINE int
335   (npop) (TPARM_STATE *tps) {
336     return npop(tps);
337 }
338 #undef npop
339 #endif
340
341 #define spop(tps) \
342     ((TPS(stack_ptr)-- > 0) \
343      ? ((!TPS(stack)[TPS(stack_ptr)].num_type \
344         && TPS(stack)[TPS(stack_ptr)].data.str != 0) \
345          ? TPS(stack)[TPS(stack_ptr)].data.str \
346          : dummy) \
347      : (tparm_error(tps, "spop: stack underflow"), \
348         dummy))
349
350 #if NCURSES_EXPANDED
351 static NCURSES_INLINE char *
352   (spop) (TPARM_STATE *tps) {
353     return spop(tps);
354 }
355 #undef spop
356 #endif
357
358 static NCURSES_INLINE const char *
359 parse_format(const char *s, char *format, int *len)
360 {
361     *len = 0;
362     if (format != 0) {
363         bool done = FALSE;
364         bool allowminus = FALSE;
365         bool dot = FALSE;
366         bool err = FALSE;
367         char *fmt = format;
368         int my_width = 0;
369         int my_prec = 0;
370         int value = 0;
371
372         *len = 0;
373         *format++ = '%';
374         while (*s != '\0' && !done) {
375             switch (*s) {
376             case 'c':           /* FALLTHRU */
377             case 'd':           /* FALLTHRU */
378             case 'o':           /* FALLTHRU */
379             case 'x':           /* FALLTHRU */
380             case 'X':           /* FALLTHRU */
381             case 's':
382 #ifdef EXP_XTERM_1005
383             case 'u':
384 #endif
385                 *format++ = *s;
386                 done = TRUE;
387                 break;
388             case '.':
389                 *format++ = *s++;
390                 if (dot) {
391                     err = TRUE;
392                 } else {        /* value before '.' is the width */
393                     dot = TRUE;
394                     my_width = value;
395                 }
396                 value = 0;
397                 break;
398             case '#':
399                 *format++ = *s++;
400                 break;
401             case ' ':
402                 *format++ = *s++;
403                 break;
404             case ':':
405                 s++;
406                 allowminus = TRUE;
407                 break;
408             case '-':
409                 if (allowminus) {
410                     *format++ = *s++;
411                 } else {
412                     done = TRUE;
413                 }
414                 break;
415             default:
416                 if (isdigit(UChar(*s))) {
417                     value = (value * 10) + (*s - '0');
418                     if (value > 10000)
419                         err = TRUE;
420                     *format++ = *s++;
421                 } else {
422                     done = TRUE;
423                 }
424             }
425         }
426
427         /*
428          * If we found an error, ignore (and remove) the flags.
429          */
430         if (err) {
431             my_width = my_prec = value = 0;
432             format = fmt;
433             *format++ = '%';
434             *format++ = *s;
435         }
436
437         /*
438          * Any value after '.' is the precision.  If we did not see '.', then
439          * the value is the width.
440          */
441         if (dot)
442             my_prec = value;
443         else
444             my_width = value;
445
446         *format = '\0';
447         /* return maximum string length in print */
448         *len = (my_width > my_prec) ? my_width : my_prec;
449     }
450     return s;
451 }
452
453 /*
454  * Analyze the string to see how many parameters we need from the varargs list,
455  * and what their types are.  We will only accept string parameters if they
456  * appear as a %l or %s format following an explicit parameter reference (e.g.,
457  * %p2%s).  All other parameters are numbers.
458  *
459  * 'number' counts coarsely the number of pop's we see in the string, and
460  * 'popcount' shows the highest parameter number in the string.  We would like
461  * to simply use the latter count, but if we are reading termcap strings, there
462  * may be cases that we cannot see the explicit parameter numbers.
463  */
464 NCURSES_EXPORT(int)
465 _nc_tparm_analyze(TERMINAL *term, const char *string, char **p_is_s, int *popcount)
466 {
467     TPARM_STATE *tps = get_tparm_state(term);
468     size_t len2;
469     int i;
470     int lastpop = -1;
471     int len;
472     int number = 0;
473     int level = -1;
474     const char *cp = string;
475
476     if (cp == 0)
477         return 0;
478
479     if ((len2 = strlen(cp)) + 2 > TPS(fmt_size)) {
480         TPS(fmt_size) += len2 + 2;
481         TPS(fmt_buff) = typeRealloc(char, TPS(fmt_size), TPS(fmt_buff));
482         if (TPS(fmt_buff) == 0)
483             return 0;
484     }
485
486     memset(p_is_s, 0, sizeof(p_is_s[0]) * NUM_PARM);
487     *popcount = 0;
488
489     while ((cp - string) < (int) len2) {
490         if (*cp == '%') {
491             cp++;
492             cp = parse_format(cp, TPS(fmt_buff), &len);
493             switch (*cp) {
494             default:
495                 break;
496
497             case 'd':           /* FALLTHRU */
498             case 'o':           /* FALLTHRU */
499             case 'x':           /* FALLTHRU */
500             case 'X':           /* FALLTHRU */
501             case 'c':           /* FALLTHRU */
502 #ifdef EXP_XTERM_1005
503             case 'u':
504 #endif
505                 if (lastpop <= 0) {
506                     tc_BUMP();
507                 }
508                 level -= 1;
509                 lastpop = -1;
510                 break;
511
512             case 'l':
513             case 's':
514                 if (lastpop > 0) {
515                     level -= 1;
516                     p_is_s[lastpop - 1] = dummy;
517                 }
518                 tc_BUMP();
519                 break;
520
521             case 'p':
522                 cp++;
523                 i = (UChar(*cp) - '0');
524                 if (i >= 0 && i <= NUM_PARM) {
525                     ++level;
526                     lastpop = i;
527                     if (lastpop > *popcount)
528                         *popcount = lastpop;
529                 }
530                 break;
531
532             case 'P':
533                 ++cp;
534                 break;
535
536             case 'g':
537                 ++level;
538                 cp++;
539                 break;
540
541             case S_QUOTE:
542                 ++level;
543                 cp += 2;
544                 lastpop = -1;
545                 break;
546
547             case L_BRACE:
548                 ++level;
549                 cp++;
550                 while (isdigit(UChar(*cp))) {
551                     cp++;
552                 }
553                 break;
554
555             case '+':
556             case '-':
557             case '*':
558             case '/':
559             case 'm':
560             case 'A':
561             case 'O':
562             case '&':
563             case '|':
564             case '^':
565             case '=':
566             case '<':
567             case '>':
568                 tc_BUMP();
569                 level -= 1;     /* pop 2, operate, push 1 */
570                 lastpop = -1;
571                 break;
572
573             case '!':
574             case '~':
575                 tc_BUMP();
576                 lastpop = -1;
577                 break;
578
579             case 'i':
580                 /* will add 1 to first (usually two) parameters */
581                 break;
582             }
583         }
584         if (*cp != '\0')
585             cp++;
586     }
587
588     if (number > NUM_PARM)
589         number = NUM_PARM;
590     return number;
591 }
592
593 /*
594  * Analyze the capability string, finding the number of parameters and their
595  * types.
596  *
597  * TODO: cache the result so that this is done once per capability per term.
598  */
599 static int
600 tparm_setup(TERMINAL *term, const char *string, TPARM_DATA *result)
601 {
602     TPARM_STATE *tps = get_tparm_state(term);
603     int rc = OK;
604
605     TPS(out_used) = 0;
606     memset(result, 0, sizeof(*result));
607
608     if (string == NULL) {
609         TR(TRACE_CALLS, ("%s: format is null", TPS(tname)));
610         rc = ERR;
611     } else {
612 #if HAVE_TSEARCH
613         TPARM_DATA *fs;
614         void *ft;
615
616         result->format = string;
617         if ((ft = tfind(result, &MyCache, cmp_format)) != 0) {
618             size_t len2;
619             fs = *(TPARM_DATA **) ft;
620             *result = *fs;
621             if ((len2 = strlen(string)) + 2 > TPS(fmt_size)) {
622                 TPS(fmt_size) += len2 + 2;
623                 TPS(fmt_buff) = typeRealloc(char, TPS(fmt_size), TPS(fmt_buff));
624                 if (TPS(fmt_buff) == 0)
625                     return ERR;
626             }
627         } else
628 #endif
629         {
630             /*
631              * Find the highest parameter-number referred to in the format
632              * string.  Use this value to limit the number of arguments copied
633              * from the variable-length argument list.
634              */
635             result->num_parsed = _nc_tparm_analyze(term, string,
636                                                    result->p_is_s,
637                                                    &(result->num_popped));
638             if (TPS(fmt_buff) == 0) {
639                 TR(TRACE_CALLS, ("%s: error in analysis", TPS(tname)));
640                 rc = ERR;
641             } else {
642                 int n;
643
644                 if (result->num_parsed > NUM_PARM)
645                     result->num_parsed = NUM_PARM;
646                 if (result->num_popped > NUM_PARM)
647                     result->num_popped = NUM_PARM;
648                 result->num_actual = max(result->num_popped, result->num_parsed);
649
650                 for (n = 0; n < result->num_actual; ++n) {
651                     if (result->p_is_s[n])
652                         result->tparm_type |= (1 << n);
653                 }
654 #if HAVE_TSEARCH
655                 if ((fs = typeCalloc(TPARM_DATA, 1)) != 0) {
656                     *fs = *result;
657                     if ((fs->format = strdup(string)) != 0) {
658                         if (tsearch(fs, &MyCache, cmp_format) != 0) {
659                             ++MyCount;
660                         } else {
661                             free(fs);
662                             rc = ERR;
663                         }
664                     } else {
665                         free(fs);
666                         rc = ERR;
667                     }
668                 } else {
669                     rc = ERR;
670                 }
671 #endif
672             }
673         }
674     }
675
676     return rc;
677 }
678
679 /*
680  * A few caps (such as plab_norm) have string-valued parms.  We'll have to
681  * assume that the caller knows the difference, since a char* and an int may
682  * not be the same size on the stack.  The normal prototype for tparm uses 9
683  * long's, which is consistent with our va_arg() usage.
684  */
685 static void
686 tparm_copy_valist(TPARM_DATA *data, int use_TPARM_ARG, va_list ap)
687 {
688     int i;
689
690     for (i = 0; i < data->num_actual; i++) {
691         if (data->p_is_s[i] != 0) {
692             char *value = va_arg(ap, char *);
693             if (value == 0)
694                 value = dummy;
695             data->p_is_s[i] = value;
696             data->param[i] = 0;
697         } else if (use_TPARM_ARG) {
698             data->param[i] = va_arg(ap, TPARM_ARG);
699         } else {
700             data->param[i] = (TPARM_ARG) va_arg(ap, int);
701         }
702     }
703 }
704
705 /*
706  * This is a termcap compatibility hack.  If there are no explicit pop
707  * operations in the string, load the stack in such a way that successive pops
708  * will grab successive parameters.  That will make the expansion of (for
709  * example) \E[%d;%dH work correctly in termcap style, which means tparam()
710  * will expand termcap strings OK.
711  */
712 static bool
713 tparm_tc_compat(TPARM_STATE *tps, TPARM_DATA *data)
714 {
715     bool termcap_hack = FALSE;
716
717     TPS(stack_ptr) = 0;
718
719     if (data->num_popped == 0) {
720         int i;
721
722         termcap_hack = TRUE;
723         for (i = data->num_parsed - 1; i >= 0; i--) {
724             if (data->p_is_s[i]) {
725                 spush(tps, data->p_is_s[i]);
726             } else {
727                 npush(tps, (int) data->param[i]);
728             }
729         }
730     }
731     return termcap_hack;
732 }
733
734 #ifdef TRACE
735 static void
736 tparm_trace_call(TPARM_STATE *tps, const char *string, TPARM_DATA *data)
737 {
738     if (USE_TRACEF(TRACE_CALLS)) {
739         int i;
740         for (i = 0; i < data->num_actual; i++) {
741             if (data->p_is_s[i] != 0) {
742                 save_text(tps, ", %s", _nc_visbuf(data->p_is_s[i]), 0);
743             } else if ((long) data->param[i] > MAX_OF_TYPE(NCURSES_INT2) ||
744                        (long) data->param[i] < 0) {
745                 _tracef("BUG: problem with tparm parameter #%d of %d",
746                         i + 1, data->num_actual);
747                 break;
748             } else {
749                 save_number(tps, ", %d", (int) data->param[i], 0);
750             }
751         }
752         _tracef(T_CALLED("%s(%s%s)"), TPS(tname), _nc_visbuf(string), TPS(out_buff));
753         TPS(out_used) = 0;
754         _nc_unlock_global(tracef);
755     }
756 }
757
758 #else
759 #define tparm_trace_call(tps, string, data)     /* nothing */
760 #endif /* TRACE */
761
762 #define init_vars(name) \
763         if (!name##_used) { \
764             name##_used = TRUE; \
765             memset(name##_vars, 0, sizeof(name##_vars)); \
766         }
767
768 static NCURSES_INLINE char *
769 tparam_internal(TPARM_STATE *tps, const char *string, TPARM_DATA *data)
770 {
771     int number;
772     int len;
773     int level;
774     int x, y;
775     int i;
776     const char *s;
777     const char *cp = string;
778     size_t len2 = strlen(cp);
779     bool incremented_two = FALSE;
780     bool termcap_hack = tparm_tc_compat(tps, data);
781     /*
782      * SVr4 curses stores variables 'A' to 'Z' in the TERMINAL structure (so
783      * they are initialized once to zero), and variables 'a' to 'z' on the
784      * stack in tparm, referring to the former as "static" and the latter as
785      * "dynamic".  However, it makes no check to ensure that the "dynamic"
786      * variables are initialized.
787      *
788      * Solaris xpg4 curses makes no distinction between the upper/lower, and
789      * stores the common set of 26 variables on the stack, without initializing
790      * them.
791      *
792      * In ncurses, both sets of variables are initialized on the first use.
793      */
794     bool dynamic_used = FALSE;
795     int dynamic_vars[NUM_VARS];
796
797     tparm_trace_call(tps, string, data);
798
799     while ((cp - string) < (int) len2) {
800         if (*cp != '%') {
801             save_char(tps, UChar(*cp));
802         } else {
803             TPS(tparam_base) = cp++;
804             cp = parse_format(cp, TPS(fmt_buff), &len);
805             switch (*cp) {
806             default:
807                 break;
808             case '%':
809                 save_char(tps, '%');
810                 break;
811
812             case 'd':           /* FALLTHRU */
813             case 'o':           /* FALLTHRU */
814             case 'x':           /* FALLTHRU */
815             case 'X':           /* FALLTHRU */
816                 x = npop(tps);
817                 save_number(tps, TPS(fmt_buff), x, len);
818                 break;
819
820             case 'c':           /* FALLTHRU */
821                 x = npop(tps);
822                 save_char(tps, x);
823                 break;
824
825 #ifdef EXP_XTERM_1005
826             case 'u':
827                 {
828                     unsigned char target[10];
829                     unsigned source = (unsigned) npop(tps);
830                     int rc = _nc_conv_to_utf8(target, source, (unsigned)
831                                               sizeof(target));
832                     int n;
833                     for (n = 0; n < rc; ++n) {
834                         save_char(tps, target[n]);
835                     }
836                 }
837                 break;
838 #endif
839             case 'l':
840                 s = spop(tps);
841                 npush(tps, (int) strlen(s));
842                 break;
843
844             case 's':
845                 s = spop(tps);
846                 save_text(tps, TPS(fmt_buff), s, len);
847                 break;
848
849             case 'p':
850                 cp++;
851                 i = (UChar(*cp) - '1');
852                 if (i >= 0 && i < NUM_PARM) {
853                     if (data->p_is_s[i]) {
854                         spush(tps, data->p_is_s[i]);
855                     } else {
856                         npush(tps, (int) data->param[i]);
857                     }
858                 }
859                 break;
860
861             case 'P':
862                 cp++;
863                 if (isUPPER(*cp)) {
864                     i = (UChar(*cp) - 'A');
865                     TPS(static_vars)[i] = npop(tps);
866                 } else if (isLOWER(*cp)) {
867                     i = (UChar(*cp) - 'a');
868                     init_vars(dynamic);
869                     dynamic_vars[i] = npop(tps);
870                 }
871                 break;
872
873             case 'g':
874                 cp++;
875                 if (isUPPER(*cp)) {
876                     i = (UChar(*cp) - 'A');
877                     npush(tps, TPS(static_vars)[i]);
878                 } else if (isLOWER(*cp)) {
879                     i = (UChar(*cp) - 'a');
880                     init_vars(dynamic);
881                     npush(tps, dynamic_vars[i]);
882                 }
883                 break;
884
885             case S_QUOTE:
886                 cp++;
887                 npush(tps, UChar(*cp));
888                 cp++;
889                 break;
890
891             case L_BRACE:
892                 number = 0;
893                 cp++;
894                 while (isdigit(UChar(*cp))) {
895                     number = (number * 10) + (UChar(*cp) - '0');
896                     cp++;
897                 }
898                 npush(tps, number);
899                 break;
900
901             case '+':
902                 y = npop(tps);
903                 x = npop(tps);
904                 npush(tps, x + y);
905                 break;
906
907             case '-':
908                 y = npop(tps);
909                 x = npop(tps);
910                 npush(tps, x - y);
911                 break;
912
913             case '*':
914                 y = npop(tps);
915                 x = npop(tps);
916                 npush(tps, x * y);
917                 break;
918
919             case '/':
920                 y = npop(tps);
921                 x = npop(tps);
922                 npush(tps, y ? (x / y) : 0);
923                 break;
924
925             case 'm':
926                 y = npop(tps);
927                 x = npop(tps);
928                 npush(tps, y ? (x % y) : 0);
929                 break;
930
931             case 'A':
932                 y = npop(tps);
933                 x = npop(tps);
934                 npush(tps, y && x);
935                 break;
936
937             case 'O':
938                 y = npop(tps);
939                 x = npop(tps);
940                 npush(tps, y || x);
941                 break;
942
943             case '&':
944                 y = npop(tps);
945                 x = npop(tps);
946                 npush(tps, x & y);
947                 break;
948
949             case '|':
950                 y = npop(tps);
951                 x = npop(tps);
952                 npush(tps, x | y);
953                 break;
954
955             case '^':
956                 y = npop(tps);
957                 x = npop(tps);
958                 npush(tps, x ^ y);
959                 break;
960
961             case '=':
962                 y = npop(tps);
963                 x = npop(tps);
964                 npush(tps, x == y);
965                 break;
966
967             case '<':
968                 y = npop(tps);
969                 x = npop(tps);
970                 npush(tps, x < y);
971                 break;
972
973             case '>':
974                 y = npop(tps);
975                 x = npop(tps);
976                 npush(tps, x > y);
977                 break;
978
979             case '!':
980                 x = npop(tps);
981                 npush(tps, !x);
982                 break;
983
984             case '~':
985                 x = npop(tps);
986                 npush(tps, ~x);
987                 break;
988
989             case 'i':
990                 /*
991                  * Increment the first two parameters -- if they are numbers
992                  * rather than strings.  As a side effect, assign into the
993                  * stack; if this is termcap, then the stack was populated
994                  * using the termcap hack above rather than via the terminfo
995                  * 'p' case.
996                  */
997                 if (!incremented_two) {
998                     incremented_two = TRUE;
999                     if (data->p_is_s[0] == 0) {
1000                         data->param[0]++;
1001                         if (termcap_hack)
1002                             TPS(stack)[0].data.num = (int) data->param[0];
1003                     }
1004                     if (data->p_is_s[1] == 0) {
1005                         data->param[1]++;
1006                         if (termcap_hack)
1007                             TPS(stack)[1].data.num = (int) data->param[1];
1008                     }
1009                 }
1010                 break;
1011
1012             case '?':
1013                 break;
1014
1015             case 't':
1016                 x = npop(tps);
1017                 if (!x) {
1018                     /* scan forward for %e or %; at level zero */
1019                     cp++;
1020                     level = 0;
1021                     while (*cp) {
1022                         if (*cp == '%') {
1023                             cp++;
1024                             if (*cp == '?')
1025                                 level++;
1026                             else if (*cp == ';') {
1027                                 if (level > 0)
1028                                     level--;
1029                                 else
1030                                     break;
1031                             } else if (*cp == 'e' && level == 0)
1032                                 break;
1033                         }
1034
1035                         if (*cp)
1036                             cp++;
1037                     }
1038                 }
1039                 break;
1040
1041             case 'e':
1042                 /* scan forward for a %; at level zero */
1043                 cp++;
1044                 level = 0;
1045                 while (*cp) {
1046                     if (*cp == '%') {
1047                         cp++;
1048                         if (*cp == '?')
1049                             level++;
1050                         else if (*cp == ';') {
1051                             if (level > 0)
1052                                 level--;
1053                             else
1054                                 break;
1055                         }
1056                     }
1057
1058                     if (*cp)
1059                         cp++;
1060                 }
1061                 break;
1062
1063             case ';':
1064                 break;
1065
1066             }                   /* endswitch (*cp) */
1067         }                       /* endelse (*cp == '%') */
1068
1069         if (*cp == '\0')
1070             break;
1071
1072         cp++;
1073     }                           /* endwhile (*cp) */
1074
1075     get_space(tps, (size_t) 1);
1076     TPS(out_buff)[TPS(out_used)] = '\0';
1077
1078     if (TPS(stack_ptr) && !_nc_tparm_err) {
1079         DEBUG(2, ("tparm: stack has %d item%s on return",
1080                   TPS(stack_ptr),
1081                   TPS(stack_ptr) == 1 ? "" : "s"));
1082         _nc_tparm_err++;
1083     }
1084
1085     T((T_RETURN("%s"), _nc_visbuf(TPS(out_buff))));
1086     return (TPS(out_buff));
1087 }
1088
1089 #if NCURSES_TPARM_VARARGS
1090
1091 NCURSES_EXPORT(char *)
1092 tparm(const char *string, ...)
1093 {
1094     TPARM_STATE *tps = get_tparm_state(cur_term);
1095     TPARM_DATA myData;
1096     char *result = NULL;
1097
1098     _nc_tparm_err = 0;
1099 #ifdef TRACE
1100     tps->tname = "tparm";
1101 #endif /* TRACE */
1102
1103     if (tparm_setup(cur_term, string, &myData) == OK) {
1104         va_list ap;
1105
1106         va_start(ap, string);
1107         tparm_copy_valist(&myData, TRUE, ap);
1108         va_end(ap);
1109
1110         result = tparam_internal(tps, string, &myData);
1111     }
1112     return result;
1113 }
1114
1115 #else /* !NCURSES_TPARM_VARARGS */
1116
1117 NCURSES_EXPORT(char *)
1118 tparm(const char *string,
1119       TPARM_ARG a1,
1120       TPARM_ARG a2,
1121       TPARM_ARG a3,
1122       TPARM_ARG a4,
1123       TPARM_ARG a5,
1124       TPARM_ARG a6,
1125       TPARM_ARG a7,
1126       TPARM_ARG a8,
1127       TPARM_ARG a9)
1128 {
1129     TPARM_STATE *tps = get_tparm_state(cur_term);
1130     TPARM_DATA myData;
1131     char *result = NULL;
1132
1133     _nc_tparm_err = 0;
1134 #ifdef TRACE
1135     tps->tname = "tparm";
1136 #endif /* TRACE */
1137
1138     if (tparm_setup(cur_term, string, &myData) == OK) {
1139
1140         myData.param[0] = a1;
1141         myData.param[1] = a2;
1142         myData.param[2] = a3;
1143         myData.param[3] = a4;
1144         myData.param[4] = a5;
1145         myData.param[5] = a6;
1146         myData.param[6] = a7;
1147         myData.param[7] = a8;
1148         myData.param[8] = a9;
1149
1150         result = tparam_internal(tps, string, &myData);
1151     }
1152     return result;
1153 }
1154
1155 #endif /* NCURSES_TPARM_VARARGS */
1156
1157 NCURSES_EXPORT(char *)
1158 tiparm(const char *string, ...)
1159 {
1160     TPARM_STATE *tps = get_tparm_state(cur_term);
1161     TPARM_DATA myData;
1162     char *result = NULL;
1163
1164     _nc_tparm_err = 0;
1165 #ifdef TRACE
1166     tps->tname = "tiparm";
1167 #endif /* TRACE */
1168
1169     if (tparm_setup(cur_term, string, &myData) == OK) {
1170         va_list ap;
1171
1172         va_start(ap, string);
1173         tparm_copy_valist(&myData, FALSE, ap);
1174         va_end(ap);
1175
1176         result = tparam_internal(tps, string, &myData);
1177     }
1178     return result;
1179 }
1180
1181 /*
1182  * The internal-use flavor ensures that the parameters are numbers, not strings
1183  */
1184 NCURSES_EXPORT(char *)
1185 _nc_tiparm(int expected, const char *string, ...)
1186 {
1187     TPARM_STATE *tps = get_tparm_state(cur_term);
1188     TPARM_DATA myData;
1189     char *result = NULL;
1190
1191     _nc_tparm_err = 0;
1192 #ifdef TRACE
1193     tps->tname = "_nc_tiparm";
1194 #endif /* TRACE */
1195
1196     if (tparm_setup(cur_term, string, &myData) == OK
1197         && myData.num_actual <= expected
1198         && myData.tparm_type == 0) {
1199         va_list ap;
1200
1201         va_start(ap, string);
1202         tparm_copy_valist(&myData, FALSE, ap);
1203         va_end(ap);
1204
1205         result = tparam_internal(tps, string, &myData);
1206     }
1207     return result;
1208 }
1209
1210 /*
1211  * Improve tic's checks by resetting the terminfo "static variables" before
1212  * calling functions which may update them.
1213  */
1214 NCURSES_EXPORT(void)
1215 _nc_reset_tparm(TERMINAL *term)
1216 {
1217     TPARM_STATE *tps = get_tparm_state(term);
1218     memset(TPS(static_vars), 0, sizeof(TPS(static_vars)));
1219 }